Compare commits

..

39 Commits

Author SHA1 Message Date
世界
18162df6de Update documentation 2022-11-21 14:28:28 +08:00
世界
0c48aaef08 Fix default dns transport strategy 2022-11-21 14:26:56 +08:00
世界
871f4aebbd Remove unused 2022-11-21 14:26:53 +08:00
世界
12ad50d427 Fix connect packet connection for mux client 2022-11-21 14:26:53 +08:00
世界
14329ad62d Remove follow in update script 2022-11-21 14:26:45 +08:00
世界
eb15e885b9 Support x/h2 v0.2.0 deadline 2022-11-21 14:26:43 +08:00
世界
a55726694a Fix tor geoip 2022-11-21 14:26:40 +08:00
世界
b0235c6257 Fix h2c transport 2022-11-21 14:26:30 +08:00
世界
9a01d52a2c Fix vmess request buffer 2022-11-21 14:21:11 +08:00
世界
2c0524578f Fix smux keep alive 2022-11-13 12:18:08 +08:00
Dreamacro
b10a8d55f3 Fix macOS Ventura process name match 2022-11-13 12:16:25 +08:00
世界
9a8de0f378 Fix decrypt xplus packet 2022-11-13 12:16:00 +08:00
世界
e5018383fe Fix copy pipe 2022-11-13 12:15:45 +08:00
世界
1e4f68b024 Update issue template 2022-11-13 12:14:20 +08:00
Fei1Yang
8c00885599 Update container action
* Fix non-native architectures build for Docker

* Tag latest image with a pre-defined tag

* Replace set-output commands
2022-11-13 12:14:19 +08:00
永雏塔菲
7cf5169c98 Add s390x architecture support
* Update debug.yml

Signed-off-by: 永雏塔菲 <108621198+taffychan@users.noreply.github.com>
2022-11-13 12:14:11 +08:00
世界
ce396466bd Add go1.18 debug build 2022-11-13 12:14:08 +08:00
世界
1635c98783 Bump version 2022-10-19 10:33:46 +08:00
Skyxim
cbcaa0f590 Check destination before udp connect 2022-10-19 10:33:28 +08:00
世界
763b93c021 Fix naive overflow 2022-10-19 10:33:28 +08:00
世界
00da3e0765 Fix sniff fragmented quic client hello 2022-10-19 10:33:28 +08:00
世界
46d7a78158 Fix ssh outbound 2022-10-19 10:31:43 +08:00
世界
de3f70195e Add binary to .gitignore 2022-10-19 10:30:05 +08:00
世界
3105b8c920 Bump version 2022-09-25 22:27:23 +08:00
世界
4c67ab1a54 Fix read source address from grpc-go 2022-09-25 22:27:23 +08:00
世界
84783c5359 Fix fqdn socks5 outbound connection 2022-09-25 14:44:39 +08:00
世界
22b16f82bd Fix missing source address from transport connection 2022-09-25 14:44:33 +08:00
世界
d2add33723 Bump version 2022-09-15 13:12:18 +08:00
世界
ab0daf31c1 Fix clash api proxy type 2022-09-15 13:11:52 +08:00
世界
3d94b948dd Fix port rule match logic 2022-09-15 13:11:20 +08:00
世界
1659ae5d79 Fix close grpc conn 2022-09-15 13:10:18 +08:00
世界
7279855b08 Bump version 2022-09-13 11:25:38 +08:00
世界
925fbca363 Fix concurrent write 2022-09-13 10:36:37 +08:00
世界
a5163e3e3c Fix hysteria inbound 2022-09-13 10:32:14 +08:00
世界
62859e0c6b Fix socks4 client 2022-09-13 10:32:12 +08:00
世界
a37cab48d2 Bump version 2022-09-10 23:13:58 +08:00
世界
c586c8f361 Fix socks4 request 2022-09-10 22:53:06 +08:00
世界
e68fa3e12d Fix processing empty dns result 2022-09-10 22:52:54 +08:00
世界
7f5b9e0e3b Run build on main branch 2022-09-10 22:52:54 +08:00
360 changed files with 2599 additions and 37873 deletions

28
.github/renovate.json vendored
View File

@@ -1,28 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"commitMessagePrefix": "[dependencies]",
"extends": [
"config:base",
":disableRateLimiting"
],
"baseBranches": [
"dev-next"
],
"golang": {
"enabled": false
},
"packageRules": [
{
"matchManagers": [
"github-actions"
],
"groupName": "github-actions"
},
{
"matchManagers": [
"dockerfile"
],
"groupName": "Dockerfile"
}
]
}

View File

@@ -3,7 +3,8 @@ name: Debug build
on: on:
push: push:
branches: branches:
- main-next - main
- dev
- dev-next - dev-next
paths-ignore: paths-ignore:
- '**.md' - '**.md'
@@ -11,7 +12,8 @@ on:
- '!.github/workflows/debug.yml' - '!.github/workflows/debug.yml'
pull_request: pull_request:
branches: branches:
- main-next - main
- dev
- dev-next - dev-next
jobs: jobs:
@@ -19,20 +21,24 @@ jobs:
name: Debug build name: Debug build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
@@ -54,21 +60,22 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: 1.18.10 go-version: 1.18.7
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }} key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test - name: Run Test
run: make run: |
go test -v ./...
cross: cross:
strategy: strategy:
matrix: matrix:
@@ -185,19 +192,19 @@ jobs:
TAGS: with_clash_api,with_quic TAGS: with_clash_api,with_quic
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
@@ -206,7 +213,7 @@ jobs:
id: build id: build
run: make run: make
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: sing-box-${{ matrix.name }} name: sing-box-${{ matrix.name }}
path: sing-box* path: sing-box*

View File

@@ -1,5 +1,8 @@
name: Build Docker Images name: Build Docker Images
on: on:
push:
tags:
- v*
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
@@ -9,20 +12,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v1
- name: Setup QEMU for Docker Buildx - name: Setup QEMU for Docker Buildx
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata - name: Docker metadata
id: metadata id: metadata
uses: docker/metadata-action@v4 uses: docker/metadata-action@v3
with: with:
images: ghcr.io/sagernet/sing-box images: ghcr.io/sagernet/sing-box
- name: Get tag to build - name: Get tag to build
@@ -35,7 +38,7 @@ jobs:
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi fi
- name: Build and release Docker images - name: Build and release Docker images
uses: docker/build-push-action@v4 uses: docker/build-push-action@v2
with: with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist target: dist

View File

@@ -3,40 +3,45 @@ name: Lint
on: on:
push: push:
branches: branches:
- main-next - dev
- dev-next
paths-ignore: paths-ignore:
- '**.md' - '**.md'
- '.github/**' - '.github/**'
- '!.github/workflows/lint.yml' - '!.github/workflows/debug.yml'
pull_request: pull_request:
branches: branches:
- main-next - dev
- dev-next
jobs: jobs:
build: build:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module - name: Cache go module
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }} key: go-${{ hashFiles('**/go.sum') }}
- name: Get dependencies
run: |
go mod download -x
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:

View File

@@ -10,8 +10,8 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: actions/setup-python@v4 - uses: actions/setup-python@v2
with: with:
python-version: 3.x python-version: 3.x
- run: pip install mkdocs-material mkdocs-static-i18n - run: pip install mkdocs-material mkdocs-static-i18n

View File

@@ -8,7 +8,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v7 - uses: actions/stale@v5
with: with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60 days-before-stale: 60

View File

@@ -3,24 +3,15 @@ linters:
enable: enable:
- gofumpt - gofumpt
- govet - govet
- gci # - gci
- staticcheck - staticcheck
- paralleltest - paralleltest
run:
skip-dirs:
- transport/simple-obfs
- transport/clashssr
- transport/cloudflaretls
- transport/shadowtls/tls
- transport/shadowtls/tls_go119
linters-settings: linters-settings:
gci: # gci:
custom-order: true # sections:
sections: # - standard
- standard # - prefix(github.com/sagernet/)
- prefix(github.com/sagernet/) # - default
- default
staticcheck: staticcheck:
go: '1.20' go: '1.19'

View File

@@ -1,7 +1,6 @@
project_name: sing-box project_name: sing-box
builds: builds:
- id: main - main: ./cmd/sing-box
main: ./cmd/sing-box
flags: flags:
- -v - -v
- -trimpath - -trimpath
@@ -10,17 +9,17 @@ builds:
gcflags: gcflags:
- all=-trimpath={{.Env.GOPATH}} - all=-trimpath={{.Env.GOPATH}}
ldflags: ldflags:
- -s -w -buildid= - -X github.com/sagernet/sing-box/constant.Commit={{ .ShortCommit }} -s -w -buildid=
tags: tags:
- with_gvisor
- with_quic - with_quic
- with_wireguard - with_wireguard
- with_utls
- with_clash_api - with_clash_api
- with_ssm_api
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
targets: targets:
- android_arm64
- android_amd64
- android_amd64_v3
- linux_amd64_v1 - linux_amd64_v1
- linux_amd64_v3 - linux_amd64_v3
- linux_arm64 - linux_arm64
@@ -34,55 +33,6 @@ builds:
- darwin_amd64_v3 - darwin_amd64_v3
- darwin_arm64 - darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
- with_ssm_api
env:
- CGO_ENABLED=1
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot: snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}" name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives: archives:
@@ -118,9 +68,6 @@ nfpms:
dst: /etc/systemd/system/sing-box@.service dst: /etc/systemd/system/sing-box@.service
- src: LICENSE - src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postinstall: release/config/postinstall.sh
postremove: release/config/postremove.sh
source: source:
enabled: false enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source' name_template: '{{ .ProjectName }}-{{ .Version }}.source'

View File

@@ -1,4 +1,4 @@
FROM golang:1.20-alpine AS builder FROM golang:1.19-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box
@@ -8,12 +8,13 @@ ENV CGO_ENABLED=0
RUN set -ex \ RUN set -ex \
&& apk add git build-base \ && apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \ && export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \ && go build -v -trimpath -tags 'no_gvisor,with_quic,with_wireguard,with_acme' \
-o /go/bin/sing-box \ -o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \ -ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
./cmd/sing-box ./cmd/sing-box
FROM alpine AS dist FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN set -ex \ RUN set -ex \
&& apk upgrade \ && apk upgrade \
&& apk add bash tzdata ca-certificates \ && apk add bash tzdata ca-certificates \

View File

@@ -1,8 +1,9 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,with_ssm_api TAGS ?= with_quic,with_wireguard,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid=" '-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
-w -s -buildid='
MAIN = ./cmd/sing-box MAIN = ./cmd/sing-box
.PHONY: test release .PHONY: test release
@@ -16,11 +17,11 @@ install:
fmt: fmt:
@gofumpt -l -w . @gofumpt -l -w .
@gofmt -s -w . @gofmt -s -w .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" . @gci write -s "standard,prefix(github.com/sagernet/),default" .
fmt_install: fmt_install:
go install -v mvdan.cc/gofumpt@latest go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest go install -v github.com/daixiang0/gci@v0.4.0
lint: lint:
GOOS=linux golangci-lint run ./... GOOS=linux golangci-lint run ./...
@@ -42,14 +43,14 @@ proto_install:
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot: snapshot:
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1 goreleaser release --rm-dist --snapshot
mkdir dist/release 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/release
ghr --delete --draft --prerelease -p 1 nightly dist/release ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist rm -r dist
release: release:
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1 goreleaser release --rm-dist --skip-publish
mkdir dist/release 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/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
@@ -60,19 +61,14 @@ release_install:
go install -v github.com/tcnksm/ghr@latest go install -v github.com/tcnksm/ghr@latest
test: test:
@go test -v ./... && \ @go test -v . && \
cd test && \ pushd test && \
go mod tidy && \ go mod tidy && \
go test -v -tags "$(TAGS_TEST)" . go test -v -tags with_quic,with_wireguard,with_grpc . && \
popd
test_stdio:
@go test -v ./... && \
cd test && \
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
clean: clean:
rm -rf bin dist sing-box rm -rf bin dist
rm -f $(shell go env GOPATH)/sing-box rm -f $(shell go env GOPATH)/sing-box
update: update:

View File

@@ -4,29 +4,23 @@ import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
type ClashServer interface { type ClashServer interface {
Service Service
Mode() string TrafficController
StoreSelected() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
}
type ClashCacheFile interface {
LoadSelected(group string) string
StoreSelected(group string, selected string) error
} }
type Tracker interface { type Tracker interface {
Leave() Leave()
} }
type TrafficController interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
}
type OutboundGroup interface { type OutboundGroup interface {
Now() string Now() string
All() []string All() []string
@@ -38,26 +32,3 @@ func OutboundTag(detour Outbound) string {
} }
return detour.Tag() return detour.Tag()
} }
type V2RayServer interface {
Service
StatsService() V2RayStatsService
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}
type SSMServer interface {
Service
RoutedConnection(metadata InboundContext, conn net.Conn) net.Conn
RoutedPacketConnection(metadata InboundContext, conn N.PacketConn) N.PacketConn
}
type ManagedShadowsocksServer interface {
Inbound
Method() string
Password() string
UpdateUsers(users []string, uPSKs []string) error
}

View File

@@ -6,7 +6,7 @@ import (
"net/netip" "net/netip"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -41,15 +41,13 @@ type InboundContext struct {
InboundDetour string InboundDetour string
LastInbound string LastInbound string
OriginDestination M.Socksaddr OriginDestination M.Socksaddr
InboundOptions option.InboundOptions DomainStrategy dns.DomainStrategy
SniffEnabled bool
SniffOverrideDestination bool
DestinationAddresses []netip.Addr DestinationAddresses []netip.Addr
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string
ProcessInfo *process.Info ProcessInfo *process.Info
// dns cache
QueryType uint16
} }
type inboundContextKey struct{} type inboundContextKey struct{}

View File

@@ -11,13 +11,12 @@ import (
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
mdns "github.com/miekg/dns" "golang.org/x/net/dns/dnsmessage"
) )
type Router interface { type Router interface {
Service Service
Inbound(tag string) (Inbound, bool)
Outbounds() []Outbound Outbounds() []Outbound
Outbound(tag string) (Outbound, bool) Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound DefaultOutbound(network string) Outbound
@@ -28,11 +27,11 @@ type Router interface {
GeoIPReader() *geoip.Reader GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
InterfaceFinder() control.InterfaceFinder InterfaceBindManager() control.BindManager
DefaultInterface() string DefaultInterface() string
AutoDetectInterface() bool AutoDetectInterface() bool
DefaultMark() int DefaultMark() int
@@ -40,29 +39,7 @@ type Router interface {
InterfaceMonitor() tun.DefaultInterfaceMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager PackageManager() tun.PackageManager
Rules() []Rule Rules() []Rule
SetTrafficController(controller TrafficController)
ClashServer() ClashServer
SetClashServer(server ClashServer)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
SSMServer() SSMServer
SetSSMServer(server SSMServer)
}
type routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
} }
type Rule interface { type Rule interface {
@@ -78,7 +55,3 @@ type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
} }
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

View File

@@ -3,10 +3,6 @@ package adapter
import ( import (
"context" "context"
"net" "net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
) )
type V2RayServerTransport interface { type V2RayServerTransport interface {
@@ -16,12 +12,6 @@ type V2RayServerTransport interface {
Close() error Close() error
} }
type V2RayServerTransportHandler interface {
N.TCPConnectionHandler
E.Handler
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
}
type V2RayClientTransport interface { type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error) DialContext(ctx context.Context) (net.Conn, error)
} }

57
box.go
View File

@@ -31,8 +31,6 @@ type Box struct {
logger log.ContextLogger logger log.ContextLogger
logFile *os.File logFile *os.File
clashServer adapter.ClashServer clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
ssmServer adapter.SSMServer
done chan struct{} done chan struct{}
} }
@@ -41,19 +39,9 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
logOptions := common.PtrValueOrDefault(options.Log) logOptions := common.PtrValueOrDefault(options.Log)
var needClashAPI bool var needClashAPI bool
var needV2RayAPI bool if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
var needSSMAPI bool
if options.Experimental != nil {
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true needClashAPI = true
} }
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
if options.Experimental.SSMAPI != nil && options.Experimental.SSMAPI.Listen != "" {
needSSMAPI = true
}
}
var logFactory log.Factory var logFactory log.Factory
var observableLogFactory log.ObservableFactory var observableLogFactory log.ObservableFactory
@@ -102,7 +90,8 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router, err := route.NewRouter( router, err := route.NewRouter(
ctx, ctx,
logFactory, logFactory.NewLogger("router"),
logFactory.NewLogger("dns"),
common.PtrValueOrDefault(options.Route), common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.DNS),
options.Inbounds, options.Inbounds,
@@ -160,28 +149,12 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
} }
var clashServer adapter.ClashServer var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
var ssmServer adapter.SSMServer
if needClashAPI { if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI)) clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil { if err != nil {
return nil, E.Cause(err, "create clash api server") return nil, E.Cause(err, "create clash api server")
} }
router.SetClashServer(clashServer) router.SetTrafficController(clashServer)
}
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
router.SetV2RayServer(v2rayServer)
}
if needSSMAPI {
ssmServer, err = experimental.NewSSMServer(router, logFactory.NewLogger("ssm-api"), common.PtrValueOrDefault(options.Experimental.SSMAPI))
if err != nil {
return nil, E.Cause(err, "create ssm api server")
}
router.SetSSMServer(ssmServer)
} }
return &Box{ return &Box{
router: router, router: router,
@@ -189,11 +162,9 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds: outbounds, outbounds: outbounds,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.NewLogger(""),
logFile: logFile, logFile: logFile,
clashServer: clashServer, clashServer: clashServer,
v2rayServer: v2rayServer,
ssmServer: ssmServer,
done: make(chan struct{}), done: make(chan struct{}),
}, nil }, nil
} }
@@ -252,18 +223,6 @@ func (s *Box) start() error {
return E.Cause(err, "start clash api server") return E.Cause(err, "start clash api server")
} }
} }
if s.v2rayServer != nil {
err = s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
if s.ssmServer != nil {
err = s.ssmServer.Start()
if err != nil {
return E.Cause(err, "start ssm api server")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil return nil
} }
@@ -285,12 +244,6 @@ func (s *Box) Close() error {
s.router, s.router,
s.logFactory, s.logFactory,
s.clashServer, s.clashServer,
s.v2rayServer,
s.ssmServer,
common.PtrOrNil(s.logFile), common.PtrOrNil(s.logFile),
) )
} }
func (s *Box) Router() adapter.Router {
return s.router
}

View File

@@ -1,20 +0,0 @@
package main
import (
"os"
"os/exec"
"github.com/sagernet/sing-box/log"
)
func main() {
findSDK()
command := exec.Command(os.Args[1], os.Args[2:]...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,81 +0,0 @@
package main
import (
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/rw"
)
var (
androidSDKPath string
androidNDKPath string
)
func findSDK() {
searchPath := []string{
"$ANDROID_HOME",
"$HOME/Android/Sdk",
"$HOME/.local/lib/android/sdk",
"$HOME/Library/Android/sdk",
}
for _, path := range searchPath {
path = os.ExpandEnv(path)
if rw.FileExists(path + "/licenses/android-sdk-license") {
androidSDKPath = path
break
}
}
if androidSDKPath == "" {
log.Fatal("android SDK not found")
}
if !findNDK() {
log.Fatal("android NDK not found")
}
os.Setenv("ANDROID_HOME", androidSDKPath)
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
os.Setenv("NDK", androidNDKPath)
os.Setenv("PATH", os.Getenv("PATH")+":"+filepath.Join(androidNDKPath, "toolchains", "llvm", "prebuilt", runtime.GOOS+"-x86_64", "bin"))
}
func findNDK() bool {
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
return true
}
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
if err != nil {
return false
}
versionNames := common.Map(ndkVersions, os.DirEntry.Name)
if len(versionNames) == 0 {
return false
}
sort.Slice(versionNames, func(i, j int) bool {
iVersions := strings.Split(versionNames[i], ".")
jVersions := strings.Split(versionNames[j], ".")
for k := 0; k < len(iVersions) && k < len(jVersions); k++ {
iVersion, _ := strconv.Atoi(iVersions[k])
jVersion, _ := strconv.Atoi(jVersions[k])
if iVersion != jVersion {
return iVersion > jVersion
}
}
return true
})
for _, versionName := range versionNames {
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
androidNDKPath = androidSDKPath + "/ndk/" + versionName
return true
}
}
return false
}

View File

@@ -69,20 +69,6 @@ func create() (*box.Box, context.CancelFunc, error) {
cancel() cancel()
return nil, nil, E.Cause(err, "create service") return nil, nil, E.Cause(err, "create service")
} }
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer func() {
signal.Stop(osSignals)
close(osSignals)
}()
go func() {
_, loaded := <-osSignals
if loaded {
cancel()
}
}()
err = instance.Start() err = instance.Start()
if err != nil { if err != nil {
cancel() cancel()
@@ -94,7 +80,6 @@ func create() (*box.Box, context.CancelFunc, error) {
func run() error { func run() error {
osSignals := make(chan os.Signal, 1) osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for { for {
instance, cancel, err := create() instance, cancel, err := create()
if err != nil { if err != nil {

View File

@@ -3,9 +3,9 @@ package main
import ( import (
"os" "os"
"runtime" "runtime"
"runtime/debug"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -25,40 +25,30 @@ func init() {
} }
func printVersion(cmd *cobra.Command, args []string) { func printVersion(cmd *cobra.Command, args []string) {
if nameOnly { var version string
os.Stdout.WriteString(C.Version + "\n") if !nameOnly {
return version = "sing-box "
} }
version := "sing-box version " + C.Version + "\n\n" version += F.ToString(C.Version)
version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n" if C.Commit != "" {
version += "." + C.Commit
var tags string
var revision string
debugInfo, loaded := debug.ReadBuildInfo()
if loaded {
for _, setting := range debugInfo.Settings {
switch setting.Key {
case "-tags":
tags = setting.Value
case "vcs.revision":
revision = setting.Value
} }
} if !nameOnly {
} version += " ("
version += runtime.Version()
if tags != "" { version += ", "
version += "Tags: " + tags + "\n" version += runtime.GOOS
} version += "/"
if revision != "" { version += runtime.GOARCH
version += "Revision: " + revision + "\n" version += ", "
} version += "CGO "
if C.CGO_ENABLED { if C.CGO_ENABLED {
version += "CGO: enabled\n" version += "enabled"
} else { } else {
version += "CGO: disabled\n" version += "disabled"
} }
version += ")"
}
version += "\n"
os.Stdout.WriteString(version) os.Stdout.WriteString(version)
} }

View File

@@ -3,7 +3,6 @@ package main
import ( import (
"os" "os"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View File

@@ -26,7 +26,7 @@ func WrapH2(err error) error {
if err == io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF {
return io.EOF return io.EOF
} }
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") { if Contains(err, "client disconnected", "body closed by handler") {
return net.ErrClosed return net.ErrClosed
} }
return err return err

View File

@@ -1,210 +0,0 @@
//go:build go1.19 && !go1.20
package badtls
import (
"crypto/cipher"
"crypto/rand"
"crypto/tls"
"encoding/binary"
"io"
"net"
"reflect"
"sync"
"sync/atomic"
"unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
type Conn struct {
*tls.Conn
writer N.ExtendedWriter
activeCall *int32
closeNotifySent *bool
version *uint16
rand io.Reader
halfAccess *sync.Mutex
halfError *error
cipher cipher.AEAD
explicitNonceLen int
halfPtr uintptr
halfSeq []byte
halfScratchBuf []byte
}
func Create(conn *tls.Conn) (TLSConn, error) {
if !handshakeComplete(conn) {
return nil, E.New("handshake not finished")
}
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawActiveCall := rawConn.FieldByName("activeCall")
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
return nil, E.New("badtls: invalid active call")
}
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
rawHalfConn := rawConn.FieldByName("out")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
}
rawVersion := rawConn.FieldByName("vers")
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
return nil, E.New("badtls: invalid version")
}
version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
return nil, E.New("badtls: invalid notify")
}
closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
return nil, E.New("badtls: bad config")
}
config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
randReader := config.Rand
if randReader == nil {
randReader = rand.Reader
}
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half mutex")
}
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
rawHalfError := rawHalfConn.FieldByName("err")
if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid half error")
}
halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid cipher interface")
}
rawHalfCipher := rawHalfCipherInterface.Elem()
aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
if !loaded {
return nil, E.New("badtls: invalid AEAD cipher")
}
var explicitNonceLen int
switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
case "tls.prefixNonceAEAD":
explicitNonceLen = aeadCipher.NonceSize()
case "tls.xorNonceAEAD":
default:
return nil, E.New("badtls: unknown cipher type: ", cipherName)
}
rawHalfSeq := rawHalfConn.FieldByName("seq")
if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
return nil, E.New("badtls: invalid seq")
}
halfSeq := rawHalfSeq.Bytes()
rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
return nil, E.New("badtls: invalid scratchBuf")
}
halfScratchBuf := rawHalfScratchBuf.Bytes()
return &Conn{
Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()),
activeCall: activeCall,
closeNotifySent: closeNotifySent,
version: version,
halfAccess: halfAccess,
halfError: halfError,
cipher: aeadCipher,
explicitNonceLen: explicitNonceLen,
rand: randReader,
halfPtr: rawHalfConn.UnsafeAddr(),
halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
}, nil
}
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
if buffer.Len() > maxPlaintext {
defer buffer.Release()
return common.Error(c.Write(buffer.Bytes()))
}
for {
x := atomic.LoadInt32(c.activeCall)
if x&1 != 0 {
return net.ErrClosed
}
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
break
}
}
defer atomic.AddInt32(c.activeCall, -2)
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
if err := *c.halfError; err != nil {
return err
}
if *c.closeNotifySent {
return errShutdown
}
dataLen := buffer.Len()
dataBytes := buffer.Bytes()
outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
outBuf[0] = 23
version := *c.version
if version == 0 {
version = tls.VersionTLS10
} else if version == tls.VersionTLS13 {
version = tls.VersionTLS12
}
binary.BigEndian.PutUint16(outBuf[1:], version)
var nonce []byte
if c.explicitNonceLen > 0 {
nonce = outBuf[5 : 5+c.explicitNonceLen]
if c.explicitNonceLen < 16 {
copy(nonce, c.halfSeq)
} else {
if _, err := io.ReadFull(c.rand, nonce); err != nil {
return err
}
}
}
if len(nonce) == 0 {
nonce = c.halfSeq
}
if *c.version == tls.VersionTLS13 {
buffer.FreeBytes()[0] = 23
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
buffer.Extend(1 + c.cipher.Overhead())
} else {
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
buffer.Extend(c.cipher.Overhead())
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
}
incSeq(c.halfPtr)
return c.writer.WriteBuffer(buffer)
}
func (c *Conn) FrontHeadroom() int {
return recordHeaderLen + c.explicitNonceLen
}
func (c *Conn) RearHeadroom() int {
return 1 + c.cipher.Overhead()
}
func (c *Conn) WriterMTU() int {
return maxPlaintext
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) UpstreamWriter() any {
return c.NetConn()
}

View File

@@ -1,12 +0,0 @@
//go:build !go1.19 || go1.20
package badtls
import (
"crypto/tls"
"os"
)
func Create(conn *tls.Conn) (TLSConn, error) {
return nil, os.ErrInvalid
}

View File

@@ -1,13 +0,0 @@
package badtls
import (
"context"
"crypto/tls"
"net"
)
type TLSConn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
}

View File

@@ -1,26 +0,0 @@
//go:build go1.19 && !go.1.20
package badtls
import (
"crypto/tls"
"reflect"
_ "unsafe"
)
const (
maxPlaintext = 16384 // maximum plaintext payload length
recordHeaderLen = 5 // record header length
)
//go:linkname errShutdown crypto/tls.errShutdown
var errShutdown error
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
func handshakeComplete(conn *tls.Conn) bool
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
func incSeq(conn uintptr)
//go:linkname valueInterface reflect.valueInterface
func valueInterface(v reflect.Value, safe bool) any

View File

@@ -3,6 +3,7 @@ package dialer
import ( import (
"context" "context"
"net" "net"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -13,7 +14,8 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
"github.com/database64128/tfo-go"
) )
var warnBindInterfaceOnUnsupportedPlatform = warning.New( var warnBindInterfaceOnUnsupportedPlatform = warning.New(
@@ -52,13 +54,10 @@ var warnTFOOnUnsupportedPlatform = warning.New(
) )
type DefaultDialer struct { type DefaultDialer struct {
dialer4 tfo.Dialer dialer tfo.Dialer
dialer6 tfo.Dialer udpDialer net.Dialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig udpListener net.ListenConfig
udpAddr4 string bindUDPAddr string
udpAddr6 string
} }
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer { func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
@@ -66,23 +65,25 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
var listener net.ListenConfig var listener net.ListenConfig
if options.BindInterface != "" { if options.BindInterface != "" {
warnBindInterfaceOnUnsupportedPlatform.Check() warnBindInterfaceOnUnsupportedPlatform.Check()
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1) bindFunc := control.BindToInterface(router.InterfaceBindManager(), options.BindInterface)
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() { } else if router.AutoDetectInterface() {
const useInterfaceName = C.IsLinux if C.IsWindows {
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) { bindFunc := control.BindToInterfaceIndexFunc(func(network, address string) int {
remoteAddr := M.ParseSocksaddr(address).Addr return router.InterfaceMonitor().DefaultInterfaceIndex(M.ParseSocksaddr(address).Addr)
if C.IsLinux {
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
}) })
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func(network, address string) string {
return router.InterfaceMonitor().DefaultInterfaceName(M.ParseSocksaddr(address).Addr)
})
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
} else if router.DefaultInterface() != "" { } else if router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1) bindFunc := control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface())
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} }
@@ -111,47 +112,22 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
if options.TCPFastOpen { if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check() warnTFOOnUnsupportedPlatform.Check()
} }
var udpFragment bool var bindUDPAddr string
if options.UDPFragment != nil { udpDialer := dialer
udpFragment = *options.UDPFragment var bindAddress netip.Addr
} else { if options.BindAddress != nil {
udpFragment = options.UDPFragmentDefault bindAddress = options.BindAddress.Build()
} }
if !udpFragment { if bindAddress.IsValid() {
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment()) dialer.LocalAddr = &net.TCPAddr{
listener.Control = control.Append(listener.Control, control.DisableUDPFragment()) IP: bindAddress.AsSlice(),
} }
var ( udpDialer.LocalAddr = &net.UDPAddr{
dialer4 = dialer IP: bindAddress.AsSlice(),
udpDialer4 = dialer
udpAddr4 string
)
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build()
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
} }
var ( bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).String()
dialer6 = dialer
udpDialer6 = dialer
udpAddr6 string
)
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build()
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
return &DefaultDialer{
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
} }
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, udpDialer, listener, bindUDPAddr}
} }
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
@@ -160,23 +136,11 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
} }
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkUDP: case N.NetworkUDP:
if !address.IsIPv6() { return d.udpDialer.DialContext(ctx, network, address.String())
return d.udpDialer4.DialContext(ctx, network, address.String())
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
}
}
if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address)
} else {
return DialSlowContext(&d.dialer6, ctx, network, address)
} }
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
} }
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsIPv6() { return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.bindUDPAddr)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
} else {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
}
} }

View File

@@ -1,141 +0,0 @@
package dialer
import (
"context"
"io"
"net"
"os"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
)
type slowOpenConn struct {
dialer *tfo.Dialer
ctx context.Context
network string
destination M.Socksaddr
conn net.Conn
create chan struct{}
err error
}
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
return dialer.DialContext(ctx, network, destination.String(), nil)
}
return &slowOpenConn{
dialer: dialer,
ctx: ctx,
network: network,
destination: destination,
create: make(chan struct{}),
}, nil
}
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
if c.conn == nil {
select {
case <-c.create:
if c.err != nil {
return 0, c.err
}
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return c.conn.Read(b)
}
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
if c.conn == nil {
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.err = E.Cause(err, "dial tcp fast open")
}
close(c.create)
return
}
return c.conn.Write(b)
}
func (c *slowOpenConn) Close() error {
return common.Close(c.conn)
}
func (c *slowOpenConn) LocalAddr() net.Addr {
if c.conn == nil {
return M.Socksaddr{}
}
return c.conn.LocalAddr()
}
func (c *slowOpenConn) RemoteAddr() net.Addr {
if c.conn == nil {
return M.Socksaddr{}
}
return c.conn.RemoteAddr()
}
func (c *slowOpenConn) SetDeadline(t time.Time) error {
if c.conn == nil {
return os.ErrInvalid
}
return c.conn.SetDeadline(t)
}
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
if c.conn == nil {
return os.ErrInvalid
}
return c.conn.SetReadDeadline(t)
}
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
if c.conn == nil {
return os.ErrInvalid
}
return c.conn.SetWriteDeadline(t)
}
func (c *slowOpenConn) Upstream() any {
return c.conn
}
func (c *slowOpenConn) ReaderReplaceable() bool {
return c.conn != nil
}
func (c *slowOpenConn) WriterReplaceable() bool {
return c.conn != nil
}
func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil
}
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
if c.conn != nil {
return bufio.Copy(c.conn, r)
}
return bufio.ReadFrom0(c, r)
}
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
if c.conn == nil {
select {
case <-c.create:
if c.err != nil {
return 0, c.err
}
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return bufio.Copy(w, c.conn)
}

View File

@@ -1,54 +1,34 @@
package tls package dialer
import ( import (
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"net" "net"
"net/netip" "net/netip"
"os" "os"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
) )
type STDClientConfig struct { type TLSDialer struct {
dialer N.Dialer
config *tls.Config config *tls.Config
} }
func (s *STDClientConfig) ServerName() string { func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) {
return s.config.ServerName if !options.Enabled {
} return nil, nil
}
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *STDClientConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, s.config)
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string var serverName string
if options.ServerName != "" { if options.ServerName != "" {
serverName = options.ServerName serverName = options.ServerName
} else if serverAddress != "" { } else if serverAddress != "" {
if _, err := netip.ParseAddr(serverName); err != nil { if _, err := netip.ParseAddr(serverName); err == nil {
serverName = serverAddress serverName = serverAddress
} }
} }
@@ -82,14 +62,14 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
tlsConfig.NextProtos = options.ALPN tlsConfig.NextProtos = options.ALPN
} }
if options.MinVersion != "" { if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion) minVersion, err := option.ParseTLSVersion(options.MinVersion)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse min_version") return nil, E.Cause(err, "parse min_version")
} }
tlsConfig.MinVersion = minVersion tlsConfig.MinVersion = minVersion
} }
if options.MaxVersion != "" { if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion) maxVersion, err := option.ParseTLSVersion(options.MaxVersion)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse max_version") return nil, E.Cause(err, "parse max_version")
} }
@@ -124,5 +104,42 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
return &STDClientConfig{&tlsConfig}, nil return &tlsConfig, nil
}
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
}
tlsConfig, err := TLSConfig(serverAddress, options)
if err != nil {
return nil, err
}
return &TLSDialer{
dialer: dialer,
config: tlsConfig,
}, nil
}
func (d *TLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if network != N.NetworkTCP {
return nil, os.ErrInvalid
}
conn, err := d.dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
return TLSClient(ctx, conn, d.config)
}
func (d *TLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
func TLSClient(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (*tls.Conn, error) {
tlsConn := tls.Client(conn, tlsConfig)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
return tlsConn, err
} }

View File

@@ -4,6 +4,7 @@ import (
"net/netip" "net/netip"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
) )
@@ -30,5 +31,8 @@ func (r *Reader) Lookup(addr netip.Addr) string {
if code != "" { if code != "" {
return code return code
} }
if !N.IsPublicAddr(addr) {
return "private"
}
return "unknown" return "unknown"
} }

View File

@@ -1,128 +0,0 @@
package json
import (
"bufio"
"io"
)
// kanged from v2ray
type commentFilterState = byte
const (
commentFilterStateContent commentFilterState = iota
commentFilterStateEscape
commentFilterStateDoubleQuote
commentFilterStateDoubleQuoteEscape
commentFilterStateSingleQuote
commentFilterStateSingleQuoteEscape
commentFilterStateComment
commentFilterStateSlash
commentFilterStateMultilineComment
commentFilterStateMultilineCommentStar
)
type CommentFilter struct {
br *bufio.Reader
state commentFilterState
}
func NewCommentFilter(reader io.Reader) io.Reader {
return &CommentFilter{br: bufio.NewReader(reader)}
}
func (v *CommentFilter) Read(b []byte) (int, error) {
p := b[:0]
for len(p) < len(b)-2 {
x, err := v.br.ReadByte()
if err != nil {
if len(p) == 0 {
return 0, err
}
return len(p), nil
}
switch v.state {
case commentFilterStateContent:
switch x {
case '"':
v.state = commentFilterStateDoubleQuote
p = append(p, x)
case '\'':
v.state = commentFilterStateSingleQuote
p = append(p, x)
case '\\':
v.state = commentFilterStateEscape
case '#':
v.state = commentFilterStateComment
case '/':
v.state = commentFilterStateSlash
default:
p = append(p, x)
}
case commentFilterStateEscape:
p = append(p, '\\', x)
v.state = commentFilterStateContent
case commentFilterStateDoubleQuote:
switch x {
case '"':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateDoubleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateDoubleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateDoubleQuote
case commentFilterStateSingleQuote:
switch x {
case '\'':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateSingleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateSingleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateSingleQuote
case commentFilterStateComment:
if x == '\n' {
v.state = commentFilterStateContent
p = append(p, '\n')
}
case commentFilterStateSlash:
switch x {
case '/':
v.state = commentFilterStateComment
case '*':
v.state = commentFilterStateMultilineComment
default:
p = append(p, '/', x)
}
case commentFilterStateMultilineComment:
switch x {
case '*':
v.state = commentFilterStateMultilineCommentStar
case '\n':
p = append(p, '\n')
}
case commentFilterStateMultilineCommentStar:
switch x {
case '/':
v.state = commentFilterStateContent
case '*':
// Stay
case '\n':
p = append(p, '\n')
default:
v.state = commentFilterStateMultilineComment
}
default:
panic("Unknown state.")
}
}
return len(p), nil
}

View File

@@ -488,7 +488,10 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
if err != nil { if err != nil {
return return
} }
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
return return
} }

View File

@@ -3,6 +3,7 @@ package mux
import ( import (
"context" "context"
"encoding/binary" "encoding/binary"
"io"
"net" "net"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -14,7 +15,6 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/task"
) )
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error { func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
@@ -26,8 +26,6 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
if err != nil { if err != nil {
return err return err
} }
var group task.Group
group.Append0(func(ctx context.Context) error {
var stream net.Conn var stream net.Conn
for { for {
stream, err = session.Accept() stream, err = session.Accept()
@@ -36,11 +34,6 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
} }
go newConnection(ctx, router, errorHandler, logger, stream, metadata) go newConnection(ctx, router, errorHandler, logger, stream, metadata)
} }
})
group.Cleanup(func() {
session.Close()
})
return group.Run(ctx)
} }
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) { func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
@@ -165,6 +158,9 @@ func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksad
if err != nil { if err != nil {
return return
} }
if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) _, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil { if err != nil {
return return
@@ -227,6 +223,9 @@ func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
if err != nil { if err != nil {
return return
} }
if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) _, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil { if err != nil {
return return

View File

@@ -28,5 +28,11 @@ type Info struct {
} }
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return findProcessInfo(searcher, ctx, network, source, destination) info, err := findProcessInfo(searcher, ctx, network, source, destination)
if err != nil {
if source.Addr().Is4In6() {
info, err = findProcessInfo(searcher, ctx, network, netip.AddrPortFrom(netip.AddrFrom4(source.Addr().As4()), source.Port()), destination)
}
}
return info, err
} }

View File

@@ -13,7 +13,6 @@ import (
type Listener struct { type Listener struct {
net.Listener net.Listener
AcceptNoHeader bool
} }
func (l *Listener) Accept() (net.Conn, error) { func (l *Listener) Accept() (net.Conn, error) {
@@ -23,7 +22,7 @@ func (l *Listener) Accept() (net.Conn, error) {
} }
bufReader := std_bufio.NewReader(conn) bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader) header, err := proxyproto.Read(bufReader)
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) { if err != nil {
return nil, err return nil, err
} }
if bufReader.Buffered() > 0 { if bufReader.Buffered() > 0 {
@@ -34,11 +33,8 @@ func (l *Listener) Accept() (net.Conn, error) {
} }
conn = bufio.NewCachedConn(conn, cache) conn = bufio.NewCachedConn(conn, cache)
} }
if header != nil {
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{ return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(), Source: M.SocksaddrFromNet(header.SourceAddr),
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(), Destination: M.SocksaddrFromNet(header.DestinationAddr),
}}, nil }}, nil
}
return conn, nil
} }

View File

@@ -1,7 +1,6 @@
package redir package redir
import ( import (
"encoding/binary"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -30,9 +29,7 @@ func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err erro
if err != nil { if err != nil {
return err return err
} }
var port [2]byte destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port)
binary.BigEndian.PutUint16(port[:], raw.Addr.Port)
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:]))
} }
return nil return nil
}) })

View File

@@ -2,11 +2,14 @@ package redir
import ( import (
"encoding/binary" "encoding/binary"
"net"
"net/netip" "net/netip"
"os"
"strconv"
"syscall" "syscall"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -29,18 +32,6 @@ func TProxy(fd uintptr, isIPv6 bool) error {
return err return err
} }
func TProxyWriteBack() control.Func {
return func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
if M.ParseSocksaddr(address).Addr.Is6() {
return syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
} else {
return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
}
})
}
}
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
controlMessages, err := unix.ParseSocketControlMessage(oob) controlMessages, err := unix.ParseSocketControlMessage(oob)
if err != nil { if err != nil {
@@ -55,3 +46,79 @@ func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
} }
return netip.AddrPort{}, E.New("not found") return netip.AddrPort{}, E.New("not found")
} }
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
rSockAddr, err := udpAddrToSockAddr(rAddr)
if err != nil {
return nil, err
}
lSockAddr, err := udpAddrToSockAddr(lAddr)
if err != nil {
return nil, err
}
fd, err := syscall.Socket(udpAddrFamily(lAddr, rAddr), syscall.SOCK_DGRAM, 0)
if err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Bind(fd, lSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Connect(fd, rSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
fdFile := os.NewFile(uintptr(fd), F.ToString("net-udp-dial-", rAddr))
defer fdFile.Close()
c, err := net.FileConn(fdFile)
if err != nil {
syscall.Close(fd)
return nil, err
}
return c.(*net.UDPConn), nil
}
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
switch {
case addr.IP.To4() != nil:
ip := [4]byte{}
copy(ip[:], addr.IP.To4())
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
default:
ip := [16]byte{}
copy(ip[:], addr.IP.To16())
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
if err != nil {
zoneID = 0
}
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
}
}
func udpAddrFamily(lAddr, rAddr *net.UDPAddr) int {
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
return syscall.AF_INET
}
return syscall.AF_INET6
}

View File

@@ -3,20 +3,19 @@
package redir package redir
import ( import (
"net"
"net/netip" "net/netip"
"os" "os"
"github.com/sagernet/sing/common/control"
) )
func TProxy(fd uintptr, isIPv6 bool) error { func TProxy(fd uintptr, isIPv6 bool) error {
return os.ErrInvalid return os.ErrInvalid
} }
func TProxyWriteBack() control.Func {
return nil
}
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
return netip.AddrPort{}, os.ErrInvalid return netip.AddrPort{}, os.ErrInvalid
} }
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
return nil, os.ErrInvalid
}

View File

@@ -20,7 +20,7 @@ type systemProxy struct {
isMixed bool isMixed bool
} }
func (p *systemProxy) update(event int) error { func (p *systemProxy) update() error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName { if p.interfaceName == newInterfaceName {
return nil return nil
@@ -88,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
port: port, port: port,
isMixed: isMixed, isMixed: isMixed,
} }
err := proxy.update(tun.EventInterfaceUpdate) err := proxy.update()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -7,7 +7,7 @@ import (
) )
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) { func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "") err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "<local>")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -11,10 +11,9 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns" "golang.org/x/net/dns/dnsmessage"
) )
func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) { func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
@@ -45,13 +44,18 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
} }
func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
var msg mDNS.Msg var parser dnsmessage.Parser
err := msg.Unpack(packet) _, err := parser.Start(packet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) { question, err := parser.Question()
if err != nil {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil domain := question.Name.String()
if question.Class == dnsmessage.ClassINET && IsDomainName(domain) {
return &adapter.InboundContext{Protocol: C.ProtocolDNS /*, Domain: domain*/}, nil
}
return nil, os.ErrInvalid
} }

6
common/sniff/domain.go Normal file
View File

@@ -0,0 +1,6 @@
package sniff
import _ "unsafe" // for linkname
//go:linkname IsDomainName net.isDomainName
func IsDomainName(domain string) bool

View File

@@ -18,11 +18,8 @@ type (
PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error)
) )
func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, sniffers ...StreamSniffer) (*adapter.InboundContext, error) {
if timeout == 0 { err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
timeout = C.ReadPayloadTimeout
}
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,81 +0,0 @@
package tls
import (
"context"
"crypto/tls"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
}
config, err := NewClient(router, serverAddress, options)
if err != nil {
return nil, err
}
return NewDialer(dialer, config), nil
}
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
if !options.Enabled {
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(router, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(router, serverAddress, options)
} else {
return NewSTDClient(serverAddress, options)
}
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
tlsConn := config.Client(conn)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
var badConn badtls.TLSConn
badConn, err = badtls.Create(stdConn)
if err == nil {
return badConn, nil
}
}
return tlsConn, nil
}
type Dialer struct {
dialer N.Dialer
config Config
}
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
return &Dialer{dialer, config}
}
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if network != N.NetworkTCP {
return nil, os.ErrInvalid
}
conn, err := d.dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
return ClientHandshake(ctx, conn, d.config)
}
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}

View File

@@ -1,12 +0,0 @@
package tls
const (
VersionTLS10 = 0x0301
VersionTLS11 = 0x0302
VersionTLS12 = 0x0303
VersionTLS13 = 0x0304
// Deprecated: SSLv3 is cryptographically broken, and is no longer
// supported by this package. See golang.org/issue/32716.
VersionSSL30 = 0x0300
)

View File

@@ -1,53 +0,0 @@
package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
)
type (
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
)
type Config interface {
ServerName() string
SetServerName(serverName string)
NextProtos() []string
SetNextProtos(nextProto []string)
Config() (*STDConfig, error)
Client(conn net.Conn) Conn
Clone() Config
}
type ServerConfig interface {
Config
adapter.Service
Server(conn net.Conn) Conn
}
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() ConnectionState
}
func ParseTLSVersion(version string) (uint16, error) {
switch version {
case "1.0":
return tls.VersionTLS10, nil
case "1.1":
return tls.VersionTLS11, nil
case "1.2":
return tls.VersionTLS12, nil
case "1.3":
return tls.VersionTLS13, nil
default:
return 0, E.New("unknown tls version:", version)
}
}

View File

@@ -1,221 +0,0 @@
//go:build with_ech
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"net/netip"
"os"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
type ECHClientConfig struct {
config *cftls.Config
}
func (e *ECHClientConfig) ServerName() string {
return e.config.ServerName
}
func (e *ECHClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (e *ECHClientConfig) NextProtos() []string {
return e.config.NextProtos
}
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
e.config.NextProtos = nextProto
}
func (e *ECHClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for ECH")
}
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
return &echConnWrapper{cftls.Client(conn, e.config)}
}
func (e *ECHClientConfig) Clone() Config {
return &ECHClientConfig{
config: e.config.Clone(),
}
}
type echConnWrapper struct {
*cftls.Conn
}
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
} else if serverAddress != "" {
if _, err := netip.ParseAddr(serverName); err != nil {
serverName = serverAddress
}
}
if serverName == "" && !options.Insecure {
return nil, E.New("missing server_name or insecure=true")
}
var tlsConfig cftls.Config
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
tlsConfig.InsecureSkipVerify = options.Insecure
} else if options.DisableSNI {
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error {
verifyOptions := x509.VerifyOptions{
DNSName: serverName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range state.PeerCertificates[1:] {
verifyOptions.Intermediates.AddCert(cert)
}
_, err := state.PeerCertificates[0].Verify(verifyOptions)
return err
}
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range cftls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", certificate)
}
tlsConfig.RootCAs = certPool
}
// ECH Config
tlsConfig.ECHEnabled = true
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
if options.ECH.Config != "" {
clientConfigContent, err := base64.StdEncoding.DecodeString(options.ECH.Config)
if err != nil {
return nil, err
}
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
if err != nil {
return nil, err
}
tlsConfig.ClientECHConfigs = clientConfig
} else {
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
}
return &ECHClientConfig{&tlsConfig}, nil
}
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: serverName + ".",
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
},
}
response, err := router.Exchange(ctx, message)
if err != nil {
return nil, err
}
if response.Rcode != mDNS.RcodeSuccess {
return nil, dns.RCodeError(response.Rcode)
}
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
for _, value := range resource.Value {
if value.Key().String() == "ech" {
echConfig, err := base64.StdEncoding.DecodeString(value.String())
if err != nil {
return nil, E.Cause(err, "decode ECH config")
}
return cftls.UnmarshalECHConfigs(echConfig)
}
}
default:
return nil, E.New("unknown resource record type: ", resource.Header().Rrtype)
}
}
return nil, E.New("no ECH config found")
}
}

View File

@@ -1,13 +0,0 @@
//go:build !with_ech
package tls
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
}

View File

@@ -1,50 +0,0 @@
package tls
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
)
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, err
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: time.Now().Add(time.Hour * -1),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if err != nil {
return nil, err
}
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}
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
}

View File

@@ -1,37 +0,0 @@
package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
return NewSTDServer(ctx, logger, options)
}
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
tlsConn := config.Server(conn)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
var badConn badtls.TLSConn
badConn, err = badtls.Create(stdConn)
if err == nil {
return badConn, nil
}
}
return tlsConn, nil
}

View File

@@ -1,171 +0,0 @@
//go:build with_utls
package tls
import (
"crypto/tls"
"crypto/x509"
"net"
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/refraction-networking/utls"
)
type UTLSClientConfig struct {
config *utls.Config
id utls.ClientHelloID
}
func (e *UTLSClientConfig) ServerName() string {
return e.config.ServerName
}
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (e *UTLSClientConfig) NextProtos() []string {
return e.config.NextProtos
}
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
e.config.NextProtos = nextProto
}
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS")
}
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
}
type utlsConnWrapper struct {
*utls.UConn
}
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
} else if serverAddress != "" {
if _, err := netip.ParseAddr(serverName); err != nil {
serverName = serverAddress
}
}
if serverName == "" && !options.Insecure {
return nil, E.New("missing server_name or insecure=true")
}
var tlsConfig utls.Config
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
tlsConfig.InsecureSkipVerify = options.Insecure
} else if options.DisableSNI {
return nil, E.New("disable_sni is unsupported in uTLS")
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", certificate)
}
tlsConfig.RootCAs = certPool
}
var id utls.ClientHelloID
switch options.UTLS.Fingerprint {
case "chrome", "":
id = utls.HelloChrome_Auto
case "firefox":
id = utls.HelloFirefox_Auto
case "edge":
id = utls.HelloEdge_Auto
case "safari":
id = utls.HelloSafari_Auto
case "360":
id = utls.Hello360_Auto
case "qq":
id = utls.HelloQQ_Auto
case "ios":
id = utls.HelloIOS_Auto
case "android":
id = utls.HelloAndroid_11_OkHttp
case "random":
id = utls.HelloRandomized
default:
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}

View File

@@ -1,13 +0,0 @@
//go:build !with_utls
package tls
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
}

View File

@@ -1,8 +0,0 @@
package constant
import "time"
const (
DHCPTTL = time.Hour
DHCPTimeout = time.Minute
)

View File

@@ -3,5 +3,3 @@ package constant
import E "github.com/sagernet/sing/common/exceptions" import E "github.com/sagernet/sing/common/exceptions"
var ErrTLSRequired = E.New("TLS required") var ErrTLSRequired = E.New("TLS required")
var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)

View File

@@ -19,11 +19,8 @@ const (
TypeTor = "tor" TypeTor = "tor"
TypeSSH = "ssh" TypeSSH = "ssh"
TypeShadowTLS = "shadowtls" TypeShadowTLS = "shadowtls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
) )
const ( const (
TypeSelector = "selector" TypeSelector = "selector"
TypeURLTest = "urltest"
) )

5
constant/quic.go Normal file
View File

@@ -0,0 +1,5 @@
//go:build with_quic
package constant
const QUIC_AVAILABLE = true

9
constant/quic_stub.go Normal file
View File

@@ -0,0 +1,9 @@
//go:build !with_quic
package constant
import E "github.com/sagernet/sing/common/exceptions"
const QUIC_AVAILABLE = false
var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)

View File

@@ -9,5 +9,4 @@ const (
QUICTimeout = 30 * time.Second QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 1 * time.Minute
) )

View File

@@ -1,3 +1,6 @@
package constant package constant
var Version = "1.2-beta2" var (
Version = "1.0.7"
Commit = ""
)

View File

@@ -1,103 +1,3 @@
#### 1.2-beta2
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add fallback support for v2ray transport
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
#### 1.2-beta1
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add v2ray [user stats](/configuration/experimental#statsusers) api
* Add new clash DNS query api
* Improve vmess request
* Fix ipv6 redirect on Linux
* Fix match geoip private
#### 1.1.5
* Add Go 1.20 support
* Fix inbound default DF value
* Fix auth_user route for naive inbound
* Fix gRPC lite header
* Ignore domain case in route rules
#### 1.1.4
* Fix DNS log
* Fix write to h2 conn after closed
* Fix create UDP DNS transport from plain IPv6 address
#### 1.1.2
* Fix http proxy auth
* Fix user from stream packet conn
* Fix DNS response TTL
* Fix override packet conn
* Skip override system proxy bypass list
* Improve DNS log
#### 1.1.1
* Fix acme config
* Fix vmess packet conn
* Suppress quic-go set DF error
#### 1.1
* Fix close clash cache
Important changes since 1.0:
* Add support for use with android VPNService
* Add tun support for WireGuard outbound
* Add system tun stack
* Add comment filter for config
* Add option for allow optional proxy protocol header
* Add Clash mode and persistence support
* Add TLS ECH and uTLS support for outbound TLS options
* Add internal simple-obfs and v2ray-plugin
* Add ShadowsocksR outbound
* Add VLESS outbound and XUDP
* Skip wait for hysteria tcp handshake response
* Add v2ray mux support for all inbound
* Add XUDP support for VMess
* Improve websocket writer
* Refine tproxy write back
* Fix DNS leak caused by
Windows' ordinary multihomed DNS resolution behavior
* Add sniff_timeout listen option
* Add custom route support for tun
* Add option for custom wireguard reserved bytes
* Split bind_address into ipv4 and ipv6
* Add ShadowTLS v1 and v2 support
#### 1.1-rc1
* Fix TLS config for h2 server
* Fix crash when input bad method in shadowsocks multi-user inbound
* Fix listen UDP
* Fix check invalid packet on macOS
#### 1.1-beta18
* Enhance defense against active probe for shadowtls server **1**
**1**:
The `fallback_after` option has been removed.
#### 1.1-beta17
* Fix shadowtls server **1**
*1*:
Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
#### 1.0.7 #### 1.0.7
* Add support for new x/h2 deadline * Add support for new x/h2 deadline
@@ -111,83 +11,6 @@ Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
* Fix udp connect for mux client * Fix udp connect for mux client
* Fix default dns transport strategy * Fix default dns transport strategy
#### 1.1-beta16
* Improve shadowtls server
* Fix default dns transport strategy
* Update uTLS to v1.2.0
#### 1.1-beta15
* Add support for new x/h2 deadline
* Fix udp connect for mux client
* Fix dns buffer
* Fix quic dns retry
* Fix create TLS config
* Fix websocket alpn
* Fix tor geoip
#### 1.1-beta14
* Add multi-user support for hysteria inbound **1**
* Add custom tls client support for std grpc
* Fix smux keep alive
* Fix vmess request buffer
* Fix default local DNS server behavior
* Fix h2c transport
*1*:
The `auth` and `auth_str` fields have been replaced by the `users` field.
#### 1.1-beta13
* Add custom worker count option for WireGuard outbound
* Split bind_address into ipv4 and ipv6
* Move WFP manipulation to strict route
* Fix WireGuard outbound panic when close
* Fix macOS Ventura process name match
* Fix QUIC connection migration by @HyNetwork
* Fix handling QUIC client SNI by @HyNetwork
#### 1.1-beta12
* Fix uTLS config
* Update quic-go to v0.30.0
* Update cloudflare-tls to go1.18.7
#### 1.1-beta11
* Add option for custom wireguard reserved bytes
* Fix shadowtls v2
* Fix h3 dns transport
* Fix copy pipe
* Fix decrypt xplus packet
* Fix v2ray api
* Suppress no network error
* Improve local dns transport
#### 1.1-beta10
* Add [sniff_timeout](/configuration/shared/listen#sniff_timeout) listen option
* Add [custom route](/configuration/inbound/tun#inet4_route_address) support for tun **1**
* Fix interface monitor
* Fix websocket headroom
* Fix uTLS handshake
* Fix ssh outbound
* Fix sniff fragmented quic client hello
* Fix DF for hysteria
* Fix naive overflow
* Check destination before udp connect
* Update uTLS to v1.1.5
* Update tfo-go to v2.0.2
* Update fsnotify to v1.6.0
* Update grpc to v1.50.1
*1*:
The `strict_route` on windows is removed.
#### 1.0.6 #### 1.0.6
* Fix ssh outbound * Fix ssh outbound
@@ -195,168 +18,29 @@ The `strict_route` on windows is removed.
* Fix naive overflow * Fix naive overflow
* Check destination before udp connect * Check destination before udp connect
#### 1.1-beta9
* Fix windows route **1**
* Add [v2ray statistics api](/configuration/experimental#v2ray-api-fields)
* Add ShadowTLS v2 support **2**
* Fixes and improvements
**1**:
* Fix DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
* Flush Windows DNS cache when start/close
**2**:
See [ShadowTLS inbound](/configuration/inbound/shadowtls#version)
and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
#### 1.1-beta8
* Fix leaks on close
* Improve websocket writer
* Refine tproxy write back
* Refine 4in6 processing
* Fix shadowsocks plugins
* Fix missing source address from transport connection
* Fix fqdn socks5 outbound connection
* Fix read source address from grpc-go
#### 1.0.5 #### 1.0.5
* Fix missing source address from transport connection * Fix missing source address from transport connection
* Fix fqdn socks5 outbound connection * Fix fqdn socks5 outbound connection
* Fix read source address from grpc-go * Fix read source address from grpc-go
#### 1.1-beta7
* Add v2ray mux and XUDP support for VMess inbound
* Add XUDP support for VMess outbound
* Disable DF on direct outbound by default
* Fix bugs in 1.1-beta6
#### 1.1-beta6
* Add [URLTest outbound](/configuration/outbound/urltest)
* Fix bugs in 1.1-beta5
#### 1.1-beta5
* Print tags in version command
* Redirect clash hello to external ui
* Move shadowsocksr implementation to clash
* Make gVisor optional **1**
* Refactor to miekg/dns
* Refactor bind control
* Fix build on go1.18
* Fix clash store-selected
* Fix close grpc conn
* Fix port rule match logic
* Fix clash api proxy type
*1*:
The build tag `no_gvisor` is replaced by `with_gvisor`.
The default tun stack is changed to system.
#### 1.0.4 #### 1.0.4
* Fix close grpc conn * Fix close grpc conn
* Fix port rule match logic * Fix port rule match logic
* Fix clash api proxy type * Fix clash api proxy type
#### 1.1-beta4
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr)
* Add [VLESS outbound and XUDP](/configuration/outbound/vless)
* Skip wait for hysteria tcp handshake response
* Fix socks4 client
* Fix hysteria inbound
* Fix concurrent write
#### 1.0.3 #### 1.0.3
* Fix socks4 client * Fix socks4 client
* Fix hysteria inbound * Fix hysteria inbound
* Fix concurrent write * Fix concurrent write
#### 1.1-beta3
* Fix using custom TLS client in http2 client
* Fix bugs in 1.1-beta2
#### 1.1-beta2
* Add Clash mode and persistence support **1**
* Add TLS ECH and uTLS support for outbound TLS options **2**
* Fix socks4 request
* Fix processing empty dns result
*1*:
Switching modes using the Clash API, and `store-selected` are now supported,
see [Experimental](/configuration/experimental).
*2*:
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
message, see [TLS#ECH](/configuration/shared/tls#ech).
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance,
see [TLS#uTLS](/configuration/shared/tls#utls).
#### 1.0.2 #### 1.0.2
* Fix socks4 request * Fix socks4 request
* Fix processing empty dns result * Fix processing empty dns result
#### 1.1-beta1
* Add support for use with android VPNService **1**
* Add tun support for WireGuard outbound **2**
* Add system tun stack **3**
* Add comment filter for config **4**
* Add option for allow optional proxy protocol header
* Add half close for smux
* Set UDP DF by default **5**
* Set default tun mtu to 9000
* Update gVisor to 20220905.0
*1*:
In previous versions, Android VPN would not work with tun enabled.
The usage of tun over VPN and VPN over tun is now supported, see [Tun Inbound](/configuration/inbound/tun#auto_route).
*2*:
In previous releases, WireGuard outbound support was backed by the lower performance gVisor virtual interface.
It achieves the same performance as wireguard-go by providing automatic system interface support.
*3*:
It does not depend on gVisor and has better performance in some cases.
It is less compatible and may not be available in some environments.
*4*:
Annotated json configuration files are now supported.
*5*:
UDP fragmentation is now blocked by default.
Including shadowsocks-libev, shadowsocks-rust and quic-go all disable segmentation by default.
See [Dial Fields](/configuration/shared/dial#udp_fragment)
and [Listen Fields](/configuration/shared/listen#udp_fragment).
#### 1.0.1 #### 1.0.1
* Fix match 4in6 address in ip_cidr * Fix match 4in6 address in ip_cidr

View File

@@ -9,11 +9,6 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -78,7 +73,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@@ -109,9 +103,7 @@
The default rule uses the following matching logic: The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -124,10 +116,6 @@ Tags of [Inbound](/configuration/inbound).
Not limited if empty. Not limited if empty.
#### query_type
DNS query type. Values can be integers or type name strings.
#### network #### network
`tcp` or `udp`. `tcp` or `udp`.
@@ -220,10 +208,6 @@ Match user name.
Match user id. Match user id.
#### clash_mode
Match Clash mode.
#### invert #### invert
Invert match result. Invert match result.

View File

@@ -9,11 +9,6 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -77,7 +72,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@@ -108,9 +102,7 @@
默认规则使用以下匹配逻辑: 默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -123,10 +115,6 @@
默认不限制。 默认不限制。
#### query_type
DNS 查询类型。值可以为整数或者类型名称字符串。
#### network #### network
`tcp``udp` `tcp``udp`
@@ -219,10 +207,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配用户 ID。 匹配用户 ID。
#### clash_mode
匹配 Clash 模式。
#### invert #### invert
反选匹配结果。 反选匹配结果。

View File

@@ -31,7 +31,7 @@ The tag of the dns server.
The address of the dns server. The address of the dns server.
| Protocol | Format | | Protocol | Format |
|----------|-------------------------------| |----------|-----------------------------|
| `System` | `local` | | `System` | `local` |
| `TCP` | `tcp://1.0.0.1` | | `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` | | `UDP` | `8.8.8.8` `udp://8.8.4.4` |
@@ -40,7 +40,6 @@ The address of the dns server.
| `QUIC` | `quic://dns.adguard.com` | | `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
!!! warning "" !!! warning ""
@@ -54,10 +53,6 @@ The address of the dns server.
the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option. the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.
!!! warning ""
DHCP transport is not included by default, see [Installation](/#installation).
| RCode | Description | | RCode | Description |
|-------------------|-----------------------| |-------------------|-----------------------|
| `success` | `No error` | | `success` | `No error` |

View File

@@ -31,7 +31,7 @@ DNS 服务器的标签。
DNS 服务器的地址。 DNS 服务器的地址。
| 协议 | 格式 | | 协议 | 格式 |
|----------|------------------------------| |----------|-----------------------------|
| `System` | `local` | | `System` | `local` |
| `TCP` | `tcp://1.0.0.1` | | `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` | | `UDP` | `8.8.8.8` `udp://8.8.4.4` |
@@ -40,7 +40,6 @@ DNS 服务器的地址。
| `QUIC` | `quic://dns.adguard.com` | | `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
!!! warning "" !!! warning ""
@@ -54,10 +53,6 @@ DNS 服务器的地址。
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。 RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
!!! warning ""
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
| RCode | 描述 | | RCode | 描述 |
|-------------------|----------| |-------------------|----------|
| `success` | `无错误` | | `success` | `无错误` |

View File

@@ -8,44 +8,25 @@
"clash_api": { "clash_api": {
"external_controller": "127.0.0.1:9090", "external_controller": "127.0.0.1:9090",
"external_ui": "folder", "external_ui": "folder",
"secret": "", "secret": ""
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! note ""
Traffic statistics and connection management can degrade performance.
### Clash API Fields ### Clash API Fields
!!! error "" !!! error ""
Clash API is not included by default, see [Installation](/#installation). Clash API is not included by default, see [Installation](/#installation).
!!! note ""
Traffic statistics and connection management will disable TCP splice in linux and reduce performance, use at your own risk.
#### external_controller #### external_controller
RESTful web API listening address. Clash API will be disabled if empty. RESTful web API listening address. Disabled if empty.
#### external_ui #### external_ui
@@ -58,51 +39,3 @@ serve it at `http://{{external-controller}}/ui`.
Secret for the RESTful API (optional) Secret for the RESTful API (optional)
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}` Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
ALWAYS set a secret if RESTful API is listening on 0.0.0.0 ALWAYS set a secret if RESTful API is listening on 0.0.0.0
#### default_mode
Default mode in clash, `rule` will be used if empty.
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
#### store_selected
!!! note ""
The tag must be set for target outbounds.
Store selected outbound for the `Selector` outbound in cache file.
#### cache_file
Cache file path, `cache.db` will be used if empty.
### V2Ray API Fields
!!! error ""
V2Ray API is not included by default, see [Installation](/#installation).
#### listen
gRPC API listening address. V2Ray API will be disabled if empty.
#### stats
Traffic statistics service settings.
#### stats.enabled
Enable statistics service.
#### stats.inbounds
Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@@ -8,44 +8,25 @@
"clash_api": { "clash_api": {
"external_controller": "127.0.0.1:9090", "external_controller": "127.0.0.1:9090",
"external_ui": "folder", "external_ui": "folder",
"secret": "", "secret": ""
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! note ""
流量统计和连接管理会降低性能。
### Clash API 字段 ### Clash API 字段
!!! error "" !!! error ""
默认安装不包含 Clash API参阅 [安装](/zh/#_2)。 默认安装不包含 Clash API参阅 [安装](/zh/#_2)。
!!! note ""
流量统计和连接管理将禁用 Linux 中的 TCP splice 并降低性能,使用风险自负。
#### external_controller #### external_controller
RESTful web API 监听地址。如果为空,则禁用 Clash API。 RESTful web API 监听地址。
#### external_ui #### external_ui
@@ -56,51 +37,3 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
RESTful API 的密钥(可选) RESTful API 的密钥(可选)
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证 通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。 如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
#### default_mode
Clash 中的默认模式,默认使用 `rule`
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
#### store_selected
!!! note ""
必须为目标出站设置标签。
`Selector` 中出站的选定的目标出站存储在缓存文件中。
#### cache_file
缓存文件路径,默认使用`cache.db`
### V2Ray API 字段
!!! error ""
默认安装不包含 V2Ray API参阅 [安装](/zh/#_2)。
#### listen
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
#### stats
流量统计服务设置。
#### stats.enabled
启用统计服务。
#### stats.inbounds
统计流量的入站列表。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

@@ -12,15 +12,8 @@
"down": "100 Mbps", "down": "100 Mbps",
"down_mbps": 100, "down_mbps": 100,
"obfs": "fuck me till the daylight", "obfs": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"auth": "", "auth": "",
"auth_str": "password" "auth_str": "password",
}
],
"recv_window_conn": 0, "recv_window_conn": 0,
"recv_window_client": 0, "recv_window_client": 0,
"max_conn_client": 0, "max_conn_client": 0,
@@ -68,19 +61,11 @@ Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
Obfuscated password. Obfuscated password.
#### users #### auth
Hysteria users
#### users.auth
==Required if `auth_str` is empty==
Authentication password, in base64. Authentication password, in base64.
#### users.auth_str #### auth_str
==Required if `auth` is empty==
Authentication password. Authentication password.

View File

@@ -12,15 +12,8 @@
"down": "100 Mbps", "down": "100 Mbps",
"down_mbps": 100, "down_mbps": 100,
"obfs": "fuck me till the daylight", "obfs": "fuck me till the daylight",
"users": [
{
"name": "sekai",
"auth": "", "auth": "",
"auth_str": "password" "auth_str": "password",
}
],
"recv_window_conn": 0, "recv_window_conn": 0,
"recv_window_client": 0, "recv_window_client": 0,
"max_conn_client": 0, "max_conn_client": 0,
@@ -68,19 +61,11 @@
混淆密码。 混淆密码。
#### users #### auth
Hysteria 用户
#### users.auth
==与 auth_str 必填一个==
base64 编码的认证密码。 base64 编码的认证密码。
#### users.auth_str #### auth_str
==与 auth 必填一个==
认证密码。 认证密码。

View File

@@ -7,8 +7,6 @@
... // Listen Fields ... // Listen Fields
"version": 2,
"password": "fuck me till the daylight",
"handshake": { "handshake": {
"server": "google.com", "server": "google.com",
"server_port": 443, "server_port": 443,
@@ -22,25 +20,12 @@
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen) for details.
### Fields ### Fields
#### version
ShadowTLS protocol version.
| Value | Protocol Version |
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
#### password
Set password.
Only available in the ShadowTLS v2 protocol.
#### handshake #### handshake
==Required== ==Required==
Handshake server address and [Dial options](/configuration/shared/dial). Handshake server address and [dial options](/configuration/shared/dial).

View File

@@ -7,8 +7,6 @@
... // 监听字段 ... // 监听字段
"version": 2,
"password": "fuck me till the daylight",
"handshake": { "handshake": {
"server": "google.com", "server": "google.com",
"server_port": 443, "server_port": 443,
@@ -24,21 +22,6 @@
### 字段 ### 字段
#### version
ShadowTLS 协议版本。
| 值 | 协议版本 |
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
#### password
设置密码。
仅在 ShadowTLS v2 协议中可用。
#### handshake #### handshake
==必填== ==必填==

View File

@@ -8,22 +8,15 @@
{ {
"type": "tun", "type": "tun",
"tag": "tun-in", "tag": "tun-in",
"interface_name": "tun0", "interface_name": "tun0",
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/128",
"mtu": 9000, "mtu": 1500,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
],
"inet6_route_address": [
"::/1",
"8000::/1"
],
"endpoint_independent_nat": false, "endpoint_independent_nat": false,
"stack": "system", "stack": "gvisor",
"include_uid": [ "include_uid": [
0 0
], ],
@@ -46,8 +39,8 @@
"exclude_package": [ "exclude_package": [
"com.android.captiveportallogin" "com.android.captiveportallogin"
], ],
...
// Listen Fields ... // Listen Fields
} }
``` ```
@@ -87,15 +80,9 @@ Set the default route to the Tun.
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface` To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
!!! note "Use with Android VPN"
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
#### strict_route #### strict_route
Enforce strict routing rules when `auto_route` is enabled: Enforce strict routing rules in Linux when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable * Let unsupported network unreachable
* Route all connections to tun * Route all connections to tun
@@ -103,27 +90,8 @@ Enforce strict routing rules when `auto_route` is enabled:
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
not be accessible by others. not be accessible by others.
*In Windows*:
* Add firewall rules to prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
#### inet4_route_address
Use custom routes instead of default when `auto_route` is enabled.
#### inet6_route_address
Use custom routes instead of default when `auto_route` is enabled.
#### endpoint_independent_nat #### endpoint_independent_nat
!!! info ""
This item is only available on the gvisor stack, other stacks are endpoint-independent NAT by default.
Enable endpoint-independent NAT. Enable endpoint-independent NAT.
Performance may degrade slightly, so it is not recommended to enable on when it is not needed. Performance may degrade slightly, so it is not recommended to enable on when it is not needed.
@@ -136,15 +104,14 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
TCP/IP stack. TCP/IP stack.
| Stack | Description | Status | | Stack | Upstream | Status |
|------------------|----------------------------------------------------------------------------------|-------------------| |------------------|-----------------------------------------------------------------------|-------------------|
| system (default) | Sometimes better performance | recommended | | gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | recommended |
| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended | | LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
!!! warning "" !!! warning ""
gVisor and LWIP stacks is not included by default, see [Installation](/#installation). The LWIP stack is not included by default, see [Installation](/#installation).
#### include_uid #### include_uid

View File

@@ -8,22 +8,15 @@
{ {
"type": "tun", "type": "tun",
"tag": "tun-in", "tag": "tun-in",
"interface_name": "tun0", "interface_name": "tun0",
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/128",
"mtu": 9000, "mtu": 1500,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
],
"inet6_route_address": [
"::/1",
"8000::/1"
],
"endpoint_independent_nat": false, "endpoint_independent_nat": false,
"stack": "system", "stack": "gvisor",
"include_uid": [ "include_uid": [
0 0
], ],
@@ -46,8 +39,8 @@
"exclude_package": [ "exclude_package": [
"com.android.captiveportallogin" "com.android.captiveportallogin"
], ],
...
// 监听字段 ... // 监听字段
} }
``` ```
@@ -87,37 +80,15 @@ tun 接口的 IPv6 前缀。
为避免流量环回,请设置 `route.auto_detect_interface``route.default_interface``outbound.bind_interface` 为避免流量环回,请设置 `route.auto_detect_interface``route.default_interface``outbound.bind_interface`
!!! note "与 Android VPN 一起使用"
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
#### strict_route #### strict_route
启用 `auto_route` 时执行严格的路由规则。 在 Linux 中启用 `auto_route` 时执行严格的路由规则。
*在 Linux 中*:
* 让不支持的网络无法到达 * 让不支持的网络无法到达
* 将所有连接路由到 tun * 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。 它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
*在 Windows 中*:
* 添加防火墙规则以阻止 Windows
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
造成的 DNS 泄露
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### inet4_route_address
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### inet6_route_address
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### endpoint_independent_nat #### endpoint_independent_nat
启用独立于端点的 NAT。 启用独立于端点的 NAT。
@@ -132,15 +103,14 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
TCP/IP 栈。 TCP/IP 栈。
| 栈 | 描述 | 状态 | | 栈 | 上游 | 状态 |
|-------------|--------------------------------------------------------------------------|-------| |------------------|-----------------------------------------------------------------------|-------|
| system (默认) | 有时性能更好 | 推荐 | | gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | 推荐 |
| gVisor | 兼容性较好,基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 | | LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
!!! warning "" !!! warning ""
默认安装不包含 gVisor 和 LWIP 栈,请参阅 [安装](/zh/#_2)。 默认安装不包含 LWIP 栈,请参阅 [安装](/zh/#_2)。
#### include_uid #### include_uid

View File

@@ -16,7 +16,7 @@
### Fields ### Fields
| Type | Format | | Type | Format |
|----------------|--------------------------------| |---------------|------------------------------|
| `direct` | [Direct](./direct) | | `direct` | [Direct](./direct) |
| `block` | [Block](./block) | | `block` | [Block](./block) |
| `socks` | [SOCKS](./socks) | | `socks` | [SOCKS](./socks) |
@@ -26,13 +26,10 @@
| `trojan` | [Trojan](./trojan) | | `trojan` | [Trojan](./trojan) |
| `wireguard` | [Wireguard](./wireguard) | | `wireguard` | [Wireguard](./wireguard) |
| `hysteria` | [Hysteria](./hysteria) | | `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `tor` | [Tor](./tor) | | `tor` | [Tor](./tor) |
| `ssh` | [SSH](./ssh) | | `ssh` | [SSH](./ssh) |
| `dns` | [DNS](./dns) | | `dns` | [DNS](./dns) |
| `selector` | [Selector](./selector) | | `selector` | [Selector](./selector) |
| `urltest` | [URLTest](./urltest) |
#### tag #### tag

View File

@@ -16,7 +16,7 @@
### 字段 ### 字段
| 类型 | 格式 | | 类型 | 格式 |
|----------------|--------------------------------| |---------------|------------------------------|
| `direct` | [Direct](./direct) | | `direct` | [Direct](./direct) |
| `block` | [Block](./block) | | `block` | [Block](./block) |
| `socks` | [SOCKS](./socks) | | `socks` | [SOCKS](./socks) |
@@ -26,13 +26,10 @@
| `trojan` | [Trojan](./trojan) | | `trojan` | [Trojan](./trojan) |
| `wireguard` | [Wireguard](./wireguard) | | `wireguard` | [Wireguard](./wireguard) |
| `hysteria` | [Hysteria](./hysteria) | | `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `tor` | [Tor](./tor) | | `tor` | [Tor](./tor) |
| `ssh` | [SSH](./ssh) | | `ssh` | [SSH](./ssh) |
| `dns` | [DNS](./dns) | | `dns` | [DNS](./dns) |
| `selector` | [Selector](./selector) | | `selector` | [Selector](./selector) |
| `urltest` | [URLTest](./urltest) |
#### tag #### tag

View File

@@ -9,8 +9,6 @@
"server_port": 1080, "server_port": 1080,
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==", "password": "8JCsPssfgS8tiRwiMlhARg==",
"plugin": "",
"plugin_opts": "",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false,
"multiplex": {}, "multiplex": {},
@@ -67,16 +65,6 @@ Legacy encryption methods:
The shadowsocks password. The shadowsocks password.
#### plugin
Shadowsocks SIP003 plugin, implemented in internal.
Only `obfs-local` and `v2ray-plugin` are supported.
#### plugin_opts
Shadowsocks SIP003 plugin options.
#### network #### network
Enabled network Enabled network

View File

@@ -9,8 +9,6 @@
"server_port": 1080, "server_port": 1080,
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==", "password": "8JCsPssfgS8tiRwiMlhARg==",
"plugin": "",
"plugin_opts": "",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false,
"multiplex": {}, "multiplex": {},
@@ -67,16 +65,6 @@
Shadowsocks 密码。 Shadowsocks 密码。
#### plugin
Shadowsocks SIP003 插件,由内部实现。
仅支持 `obfs-local``v2ray-plugin`
#### plugin_opts
Shadowsocks SIP003 插件参数。
#### network #### network
启用的网络协议 启用的网络协议

View File

@@ -1,106 +0,0 @@
### Structure
```json
{
"type": "shadowsocksr",
"tag": "ssr-out",
"server": "127.0.0.1",
"server_port": 1080,
"method": "aes-128-cfb",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"obfs": "plain",
"obfs_param": "",
"protocol": "origin",
"protocol_param": "",
"network": "udp",
... // Dial Fields
}
```
!!! warning ""
The ShadowsocksR protocol is obsolete and unmaintained. This outbound is provided for compatibility only.
!!! warning ""
ShadowsocksR is not included by default, see [Installation](/#installation).
### Fields
#### server
==Required==
The server address.
#### server_port
==Required==
The server port.
#### method
==Required==
Encryption methods:
* `aes-128-ctr`
* `aes-192-ctr`
* `aes-256-ctr`
* `aes-128-cfb`
* `aes-192-cfb`
* `aes-256-cfb`
* `rc4-md5`
* `chacha20-ietf`
* `xchacha20`
#### password
==Required==
The shadowsocks password.
#### obfs
The ShadowsocksR obfuscate.
* plain
* http_simple
* http_post
* random_head
* tls1.2_ticket_auth
#### obfs_param
The ShadowsocksR obfuscate parameter.
#### protocol
The ShadowsocksR protocol.
* origin
* verify_sha1
* auth_sha1_v4
* auth_aes128_md5
* auth_aes128_sha1
* auth_chain_a
* auth_chain_b
#### protocol_param
The ShadowsocksR protocol parameter.
#### network
Enabled network
One of `tcp` `udp`.
Both is enabled by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial) for details.

View File

@@ -1,106 +0,0 @@
### 结构
```json
{
"type": "shadowsocksr",
"tag": "ssr-out",
"server": "127.0.0.1",
"server_port": 1080,
"method": "aes-128-cfb",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"obfs": "plain",
"obfs_param": "",
"protocol": "origin",
"protocol_param": "",
"network": "udp",
... // 拨号字段
}
```
!!! warning ""
ShadowsocksR 协议已过时且无人维护。 提供此出站仅出于兼容性目的。
!!! warning ""
默认安装不包含被 ShadowsocksR参阅 [安装](/zh/#_2)。
### 字段
#### server
==必填==
服务器地址。
#### server_port
==必填==
服务器端口。
#### method
==必填==
加密方法:
* `aes-128-ctr`
* `aes-192-ctr`
* `aes-256-ctr`
* `aes-128-cfb`
* `aes-192-cfb`
* `aes-256-cfb`
* `rc4-md5`
* `chacha20-ietf`
* `xchacha20`
#### password
==必填==
Shadowsocks 密码。
#### obfs
ShadowsocksR 混淆。
* plain
* http_simple
* http_post
* random_head
* tls1.2_ticket_auth
#### obfs_param
ShadowsocksR 混淆参数。
#### protocol
ShadowsocksR 协议。
* origin
* verify_sha1
* auth_sha1_v4
* auth_aes128_md5
* auth_aes128_sha1
* auth_chain_a
* auth_chain_b
#### protocol_param
ShadowsocksR 协议参数。
#### network
启用的网络协议
`tcp``udp`
默认所有。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@@ -7,8 +7,6 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"version": 3,
"password": "fuck me till the daylight",
"tls": {}, "tls": {},
... // Dial Fields ... // Dial Fields
@@ -29,22 +27,6 @@ The server address.
The server port. The server port.
#### version
ShadowTLS protocol version.
| Value | Protocol Version |
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
Set password.
Only available in the ShadowTLS v2/v3 protocol.
#### tls #### tls
==Required== ==Required==

View File

@@ -7,8 +7,6 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"version": 3,
"password": "fuck me till the daylight",
"tls": {}, "tls": {},
... // 拨号字段 ... // 拨号字段
@@ -29,22 +27,6 @@
服务器端口。 服务器端口。
#### version
ShadowTLS 协议版本。
| 值 | 协议版本 |
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
#### password
设置密码。
仅在 ShadowTLS v2/v3 协议中可用。
#### tls #### tls
==必填== ==必填==

View File

@@ -12,9 +12,6 @@
"private_key": "", "private_key": "",
"private_key_path": "$HOME/.ssh/id_rsa", "private_key_path": "$HOME/.ssh/id_rsa",
"private_key_passphrase": "", "private_key_passphrase": "",
"host_key": [
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
],
"host_key_algorithms": [], "host_key_algorithms": [],
"client_version": "SSH-2.0-OpenSSH_7.4p1", "client_version": "SSH-2.0-OpenSSH_7.4p1",
@@ -54,10 +51,6 @@ Private key path.
Private key passphrase. Private key passphrase.
#### host_key
Host key. Accept any if empty.
#### host_key_algorithms #### host_key_algorithms
Host key algorithms. Host key algorithms.

View File

@@ -12,9 +12,6 @@
"private_key": "", "private_key": "",
"private_key_path": "$HOME/.ssh/id_rsa", "private_key_path": "$HOME/.ssh/id_rsa",
"private_key_passphrase": "", "private_key_passphrase": "",
"host_key": [
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
],
"host_key_algorithms": [], "host_key_algorithms": [],
"client_version": "SSH-2.0-OpenSSH_7.4p1", "client_version": "SSH-2.0-OpenSSH_7.4p1",
@@ -54,10 +51,6 @@ SSH 用户, 默认使用 root。
密钥密码。 密钥密码。
#### host_key
主机密钥,留空接受所有。
#### host_key_algorithms #### host_key_algorithms
主机密钥算法。 主机密钥算法。

View File

@@ -1,37 +0,0 @@
### Structure
```json
{
"type": "urltest",
"tag": "auto",
"outbounds": [
"proxy-a",
"proxy-b",
"proxy-c"
],
"url": "http://www.gstatic.com/generate_204",
"interval": "1m",
"tolerance": 50
}
```
### Fields
#### outbounds
==Required==
List of outbound tags to test.
#### url
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
#### interval
The test interval. `1m` will be used if empty.
#### tolerance
The test tolerance in milliseconds. `50` will be used if empty.

View File

@@ -1,37 +0,0 @@
### 结构
```json
{
"type": "urltest",
"tag": "auto",
"outbounds": [
"proxy-a",
"proxy-b",
"proxy-c"
],
"url": "http://www.gstatic.com/generate_204",
"interval": "1m",
"tolerance": 50
}
```
### 字段
#### outbounds
==必填==
用于测试的出站标签列表。
#### url
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`
#### interval
测试间隔。 默认使用 `1m`
#### tolerance
以毫秒为单位的测试容差。 默认使用 `50`

View File

@@ -1,70 +0,0 @@
### Structure
```json
{
"type": "vless",
"tag": "vless-out",
"server": "127.0.0.1",
"server_port": 1080,
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"network": "tcp",
"tls": {},
"packet_encoding": "",
"transport": {},
... // Dial Fields
}
```
!!! warning ""
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
### Fields
#### server
==Required==
The server address.
#### server_port
==Required==
The server port.
#### uuid
==Required==
The VLESS user id.
#### network
Enabled network
One of `tcp` `udp`.
Both is enabled by default.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
#### packet_encoding
| Encoding | Description |
|------------|-----------------------|
| (none) | Disabled |
| packetaddr | Supported by v2ray 5+ |
| xudp | Supported by xray |
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
### Dial Fields
See [Dial Fields](/configuration/shared/dial) for details.

View File

@@ -1,70 +0,0 @@
### 结构
```json
{
"type": "vless",
"tag": "vless-out",
"server": "127.0.0.1",
"server_port": 1080,
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"network": "tcp",
"tls": {},
"packet_encoding": "",
"transport": {},
... // 拨号字段
}
```
!!! warning ""
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
### 字段
#### server
==必填==
服务器地址。
#### server_port
==必填==
服务器端口。
#### uuid
==必填==
VLESS 用户 ID。
#### network
启用的网络协议。
`tcp``udp`
默认所有。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
#### packet_encoding
| 编码 | 描述 |
|------------|---------------|
| (空) | 禁用 |
| packetaddr | 由 v2ray 5+ 支持 |
| xudp | 由 xray 支持 |
#### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@@ -14,7 +14,7 @@
"authenticated_length": true, "authenticated_length": true,
"network": "tcp", "network": "tcp",
"tls": {}, "tls": {},
"packet_encoding": "", "packet_addr": false,
"multiplex": {}, "multiplex": {},
"transport": {}, "transport": {},
@@ -84,13 +84,9 @@ Both is enabled by default.
TLS configuration, see [TLS](/configuration/shared/tls/#outbound). TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
#### packet_encoding #### packet_addr
| Encoding | Description | Enable packetaddr support.
|------------|-----------------------|
| (none) | Disabled |
| packetaddr | Supported by v2ray 5+ |
| xudp | Supported by xray |
#### multiplex #### multiplex

View File

@@ -14,7 +14,7 @@
"authenticated_length": true, "authenticated_length": true,
"network": "tcp", "network": "tcp",
"tls": {}, "tls": {},
"packet_encoding": "", "packet_addr": false,
"multiplex": {}, "multiplex": {},
"transport": {}, "transport": {},
@@ -84,13 +84,9 @@ VMess 用户 ID。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
#### packet_encoding #### packet_addr
| 编码 | 描述 | 启用 packetaddr 支持。
|------------|---------------|
| (空) | 禁用 |
| packetaddr | 由 v2ray 5+ 支持 |
| xudp | 由 xray 支持 |
#### multiplex #### multiplex

View File

@@ -7,16 +7,13 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"system_interface": false,
"interface_name": "wg0",
"local_address": [ "local_address": [
"10.0.0.1",
"10.0.0.2/32" "10.0.0.2/32"
], ],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=", "pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
"reserved": [0, 0, 0],
"workers": 4,
"mtu": 1408, "mtu": 1408,
"network": "tcp", "network": "tcp",
@@ -28,10 +25,6 @@
WireGuard is not included by default, see [Installation](/#installation). WireGuard is not included by default, see [Installation](/#installation).
!!! warning ""
gVisor, which is required by the unprivileged WireGuard is not included by default, see [Installation](/#installation).
### Fields ### Fields
#### server #### server
@@ -46,23 +39,11 @@ The server address.
The server port. The server port.
#### system_interface
Use system tun support.
Requires privilege and cannot conflict with system interfaces.
Forced if gVisor not included in the build.
#### interface_name
Custom device name when `system_interface` enabled.
#### local_address #### local_address
==Required== ==Required==
List of IP (v4 or v6) address prefixes to be assigned to the interface. List of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface.
#### private_key #### private_key
@@ -85,21 +66,9 @@ WireGuard peer public key.
WireGuard pre-shared key. WireGuard pre-shared key.
#### reserved
WireGuard reserved field bytes.
#### workers
WireGuard worker count.
CPU count is used by default.
#### mtu #### mtu
WireGuard MTU. WireGuard MTU. 1408 will be used if empty.
1408 will be used if empty.
#### network #### network

View File

@@ -7,16 +7,13 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"system_interface": false,
"interface_name": "wg0",
"local_address": [ "local_address": [
"10.0.0.1",
"10.0.0.2/32" "10.0.0.2/32"
], ],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=", "pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
"reserved": [0, 0, 0],
"workers": 4,
"mtu": 1408, "mtu": 1408,
"network": "tcp", "network": "tcp",
@@ -28,10 +25,6 @@
默认安装不包含 WireGuard, 参阅 [安装](/zh/#_2)。 默认安装不包含 WireGuard, 参阅 [安装](/zh/#_2)。
!!! warning ""
默认安装不包含被非特权 WireGuard 需要的 gVisor, 参阅 [安装](/zh/#_2)。
### 字段 ### 字段
#### server #### server
@@ -46,25 +39,13 @@
服务器端口。 服务器端口。
#### system_interface
使用系统 tun 支持。
需要特权且不能与系统接口冲突。
如果 gVisor 未包含在构建中,则强制执行。
#### interface_name
启用 `system_interface` 时的自定义设备名称。
#### local_address #### local_address
==必填== ==必填==
接口的 IPv4/IPv6 地址或地址段的列表您。 接口的 IPv4/IPv6 地址或地址段的列表您。
要分配给接口的 IPv4 或 v6地址列表。 要分配给接口的 IPv4 或 v6地址列表(可以选择带有 CIDR 掩码)
#### private_key #### private_key
@@ -87,21 +68,9 @@ WireGuard 对等公钥。
WireGuard 预共享密钥。 WireGuard 预共享密钥。
#### reserved
WireGuard 保留字段字节。
#### workers
WireGuard worker 数量。
默认使用 CPU 数量。
#### mtu #### mtu
WireGuard MTU。 WireGuard MTU。 默认1408。
默认使用 1408。
#### network #### network

View File

@@ -10,7 +10,6 @@
"rules": [], "rules": [],
"final": "", "final": "",
"auto_detect_interface": false, "auto_detect_interface": false,
"override_android_vpn": false,
"default_interface": "en0", "default_interface": "en0",
"default_mark": 233 "default_mark": 233
} }
@@ -35,25 +34,17 @@ Default outbound tag. the first outbound will be used if empty.
Only supported on Linux, Windows and macOS. Only supported on Linux, Windows and macOS.
Bind outbound connections to the default NIC by default to prevent routing loops under tun. Bind outbound connections to the default NIC by default to prevent routing loops under Tun.
Takes no effect if `outbound.bind_interface` is set. Takes no effect if `outbound.bind_interface` is set.
#### override_android_vpn
!!! error ""
Only supported on Android.
Accept Android VPN as upstream NIC when `auto_detect_interface` enabled.
#### default_interface #### default_interface
!!! error "" !!! error ""
Only supported on Linux, Windows and macOS. Only supported on Linux, Windows and macOS.
Bind outbound connections to the specified NIC by default to prevent routing loops under tun. Bind outbound connections to the specified NIC by default to prevent routing loops under Tun.
Takes no effect if `auto_detect_interface` is set. Takes no effect if `auto_detect_interface` is set.

View File

@@ -10,7 +10,6 @@
"rules": [], "rules": [],
"final": "", "final": "",
"auto_detect_interface": false, "auto_detect_interface": false,
"override_android_vpn": false,
"default_interface": "en0", "default_interface": "en0",
"default_mark": 233 "default_mark": 233
} }
@@ -35,25 +34,17 @@
仅支持 Linux、Windows 和 macOS。 仅支持 Linux、Windows 和 macOS。
默认将出站连接绑定到默认网卡,以防止在 tun 下出现路由环路。 默认将出站连接绑定到默认网卡,以防止在 Tun 下出现路由环路。
如果设置了 `outbound.bind_interface` 设置,则不生效。 如果设置了 `outbound.bind_interface` 设置,则不生效。
#### override_android_vpn
!!! error ""
仅支持 Android。
启用 `auto_detect_interface` 时接受 Android VPN 作为上游网卡。
#### default_interface #### default_interface
!!! error "" !!! error ""
仅支持 Linux、Windows 和 macOS。 仅支持 Linux、Windows 和 macOS。
默认将出站连接绑定到指定网卡,以防止在 tun 下出现路由环路。 默认将出站连接绑定到指定网卡,以防止在 Tun 下出现路由环路。
如果设置了 `auto_detect_interface` 设置,则不生效。 如果设置了 `auto_detect_interface` 设置,则不生效。

View File

@@ -80,7 +80,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": "direct" "outbound": "direct"
}, },
@@ -107,9 +106,7 @@
The default rule uses the following matching logic: The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -222,10 +219,6 @@ Match user name.
Match user id. Match user id.
#### clash_mode
Match Clash mode.
#### invert #### invert
Invert match result. Invert match result.

View File

@@ -78,7 +78,6 @@
"user_id": [ "user_id": [
1000 1000
], ],
"clash_mode": "direct",
"invert": false, "invert": false,
"outbound": "direct" "outbound": "direct"
}, },
@@ -105,9 +104,7 @@
默认规则使用以下匹配逻辑: 默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields` `other fields`
#### inbound #### inbound
@@ -220,10 +217,6 @@
匹配用户 ID。 匹配用户 ID。
#### clash_mode
匹配 Clash 模式。
#### invert #### invert
反选匹配结果。 反选匹配结果。

View File

@@ -4,13 +4,11 @@
{ {
"detour": "upstream-out", "detour": "upstream-out",
"bind_interface": "en0", "bind_interface": "en0",
"inet4_bind_address": "0.0.0.0", "bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "5s", "connect_timeout": "5s",
"tcp_fast_open": false, "tcp_fast_open": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms" "fallback_delay": "300ms"
} }
@@ -19,8 +17,8 @@
### Fields ### Fields
| Field | Available Context | | Field | Available Context |
|----------------------------------------------------------------------------------------------------------------------|-------------------| |-----------------------------------------------------------------------------------|-------------------|
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set | | `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` /`connect_timeout` | `detour` not set |
#### detour #### detour
@@ -30,13 +28,9 @@ The tag of the upstream outbound.
The network interface to bind to. The network interface to bind to.
#### inet4_bind_address #### bind_address
The IPv4 address to bind to. The address to bind to.
#### inet6_bind_address
The IPv6 address to bind to.
#### routing_mark #### routing_mark
@@ -50,14 +44,6 @@ Set netfilter routing mark.
Reuse listener address. Reuse listener address.
#### tcp_fast_open
Enable TCP Fast Open.
#### udp_fragment
Enable UDP fragmentation.
#### connect_timeout #### connect_timeout
Connect timeout, in golang's Duration format. Connect timeout, in golang's Duration format.

View File

@@ -4,13 +4,11 @@
{ {
"detour": "upstream-out", "detour": "upstream-out",
"bind_interface": "en0", "bind_interface": "en0",
"inet4_bind_address": "0.0.0.0", "bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "5s", "connect_timeout": "5s",
"tcp_fast_open": false, "tcp_fast_open": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms" "fallback_delay": "300ms"
} }
@@ -18,11 +16,6 @@
### 字段 ### 字段
| 字段 | 可用上下文 |
|----------------------------------------------------------------------------------------------------------------------|--------------|
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
#### detour #### detour
上游出站的标签。 上游出站的标签。
@@ -33,13 +26,9 @@
要绑定到的网络接口。 要绑定到的网络接口。
#### inet4_bind_address #### bind_address
要绑定的 IPv4 地址。 要绑定的地址。
#### inet6_bind_address
要绑定的 IPv6 地址。
#### routing_mark #### routing_mark
@@ -53,14 +42,6 @@
重用监听地址。 重用监听地址。
#### tcp_fast_open
启用 TCP Fast Open。
#### udp_fragment
启用 UDP 分段。
#### connect_timeout #### connect_timeout
连接超时,采用 golang 的 Duration 格式。 连接超时,采用 golang 的 Duration 格式。

View File

@@ -5,14 +5,11 @@
"listen": "::", "listen": "::",
"listen_port": 5353, "listen_port": 5353,
"tcp_fast_open": false, "tcp_fast_open": false,
"udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
"proxy_protocol": false, "proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in" "detour": "another-in"
} }
``` ```
@@ -20,13 +17,12 @@
### Fields ### Fields
| Field | Available Context | | Field | Available Context |
|-----------------------------------|-------------------------------------------------------------------| |------------------|-------------------------------------------------------------------|
| `listen` | Needs to listen on TCP or UDP. | | `listen` | Needs to listen on TCP or UDP. |
| `listen_port` | 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_fast_open` | Needs to listen on TCP. |
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. | | `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `proxy_protocol` | Needs to listen on TCP. | | `proxy_protocol` | Needs to listen on TCP. |
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
#### listen #### listen
@@ -40,11 +36,7 @@ Listen port.
#### tcp_fast_open #### tcp_fast_open
Enable TCP Fast Open. Enable tcp fast open for listener.
#### udp_fragment
Enable UDP fragmentation.
#### sniff #### sniff
@@ -58,12 +50,6 @@ Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work. If the domain name is invalid (like tor), this will not work.
#### sniff_timeout
Timeout for sniffing.
300ms is used by default.
#### domain_strategy #### domain_strategy
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
@@ -80,10 +66,6 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header. 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 #### detour
If set, connections will be forwarded to the specified inbound. If set, connections will be forwarded to the specified inbound.

View File

@@ -5,27 +5,21 @@
"listen": "::", "listen": "::",
"listen_port": 5353, "listen_port": 5353,
"tcp_fast_open": false, "tcp_fast_open": false,
"udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in" "detour": "another-in"
} }
``` ```
| 字段 | 可用上下文 | | 字段 | 可用上下文 |
|-----------------------------------|-------------------------------------| |------------------|-------------------------------------|
| `listen` | 需要监听 TCP 或 UDP。 | | `listen` | 需要监听 TCP 或 UDP。 |
| `listen_port` | 需要监听 TCP 或 UDP。 | | `listen_port` | 需要监听 TCP 或 UDP。 |
| `tcp_fast_open` | 需要监听 TCP。 | | `tcp_fast_open` | 需要监听 TCP。 |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 | | `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
| `proxy_protocol` | 需要监听 TCP。 | | `proxy_protocol` | 需要监听 TCP。 |
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
### 字段 ### 字段
@@ -41,11 +35,7 @@
#### tcp_fast_open #### tcp_fast_open
启用 TCP Fast Open 为监听器启用 TCP 快速打开
#### udp_fragment
启用 UDP 分段。
#### sniff #### sniff
@@ -59,12 +49,6 @@
如果域名无效(如 Tor将不生效。 如果域名无效(如 Tor将不生效。
#### sniff_timeout
探测超时时间。
默认使用 300ms。
#### domain_strategy #### domain_strategy
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only` 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
@@ -81,10 +65,6 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。 解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
#### proxy_protocol_accept_no_header
接受没有代理协议标头的连接。
#### detour #### detour
如果设置,连接将被转发到指定的入站。 如果设置,连接将被转发到指定的入站。

View File

@@ -30,6 +30,10 @@
} }
``` ```
!!! warning ""
ACME is not included by default, see [Installation](/#installation).
### Outbound ### Outbound
```json ```json
@@ -43,17 +47,7 @@
"max_version": "", "max_version": "",
"cipher_suites": [], "cipher_suites": [],
"certificate": "", "certificate": "",
"certificate_path": "", "certificate_path": ""
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"config": ""
},
"utls": {
"enabled": false,
"fingerprint": ""
}
} }
``` ```
@@ -161,53 +155,8 @@ The server private key, in PEM format.
The path to the server private key, in PEM format. The path to the server private key, in PEM format.
#### ech
==Client only==
!!! warning ""
ECH is not included by default, see [Installation](/#installation).
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
message.
If you don't know how to fill in the other configuration, just set `enabled`.
#### utls
==Client only==
!!! warning ""
uTLS is not included by default, see [Installation](/#installation).
!!! note ""
uTLS is poorly maintained and the effect may be unproven, use at your own risk.
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
Available fingerprint values:
* chrome
* firefox
* edge
* safari
* 360
* qq
* ios
* android
* random
Chrome fingerprint will be used if empty.
### ACME Fields ### ACME Fields
!!! warning ""
ACME is not included by default, see [Installation](/#installation).
#### domain #### domain
List of domain. List of domain.
@@ -256,6 +205,10 @@ listener for the HTTP challenge.
The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to
succeed. succeed.
### Reload
For server configuration, certificate and key will be automatically reloaded if modified.
#### external_account #### external_account
EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known
@@ -274,7 +227,3 @@ The key identifier.
#### external_account.mac_key #### external_account.mac_key
The MAC key. The MAC key.
### Reload
For server configuration, certificate and key will be automatically reloaded if modified.

View File

@@ -30,6 +30,10 @@
} }
``` ```
!!! warning ""
默认安装不包含 ACME参阅 [安装](/zh/#_2)。
### 出站 ### 出站
```json ```json
@@ -43,17 +47,7 @@
"max_version": "", "max_version": "",
"cipher_suites": [], "cipher_suites": [],
"certificate": "", "certificate": "",
"certificate_path": "", "certificate_path": ""
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"config": ""
},
"utls": {
"enabled": false,
"fingerprint": ""
}
} }
``` ```
@@ -161,53 +155,8 @@ TLS 版本值:
服务器 PEM 私钥路径。 服务器 PEM 私钥路径。
#### ech
==仅客户端==
!!! warning ""
默认安装不包含 ECH, 参阅 [安装](/zh/#_2)。
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
信息。
如果您不知道如何填写其他配置,只需设置 `enabled` 即可。
#### utls
==仅客户端==
!!! warning ""
默认安装不包含 uTLS, 参阅 [安装](/zh/#_2)。
!!! note ""
uTLS 维护不善且其效果可能未经证实,使用风险自负。
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
可用的指纹值:
* chrome
* firefox
* edge
* safari
* 360
* qq
* ios
* android
* random
默认使用 chrome 指纹。
### ACME 字段 ### ACME 字段
!!! warning ""
默认安装不包含 ACME参阅 [安装](/zh/#_2)。
#### domain #### domain
一组域名。 一组域名。
@@ -254,6 +203,10 @@ ACME 数据目录。
用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。 用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。
### Reload
对于服务器配置,如果修改,证书和密钥将自动重新加载。
#### external_account #### external_account
EAB外部帐户绑定包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA。 EAB外部帐户绑定包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA。
@@ -270,7 +223,3 @@ EAB外部帐户绑定包含将 ACME 帐户绑定或映射到其他已知
#### external_account.mac_key #### external_account.mac_key
MAC 密钥。 MAC 密钥。
### 重载
对于服务器配置,如果修改,证书和密钥将自动重新加载。

View File

@@ -53,6 +53,17 @@ we need to do this.
The library needs to be updated with the upstream. The library needs to be updated with the upstream.
#### certmagic
Link: [GitHub repository](https://github.com/SagerNet/certmagic)
Fork of `caddyserver/certmagic`
Since upstream uses `miekg/dns` and we use `x/net/dnsmessage`, we need to replace its DNS part with our own
implementation.
The library needs to be updated with the upstream.
#### smux #### smux
Link: [GitHub repository](https://github.com/SagerNet/smux) Link: [GitHub repository](https://github.com/SagerNet/smux)

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