mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
171 Commits
v0.1.0
...
v1.1-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d044232af | ||
|
|
aa7e85caa7 | ||
|
|
46a8f24400 | ||
|
|
87bc292296 | ||
|
|
ac539ace70 | ||
|
|
a15b13978f | ||
|
|
0c975db0a6 | ||
|
|
cb4fea0240 | ||
|
|
8e7957d440 | ||
|
|
f7bed32c6f | ||
|
|
ef7f2d82c0 | ||
|
|
7aa97a332e | ||
|
|
7c30dde96b | ||
|
|
9cef2a0a8f | ||
|
|
f376683fc3 | ||
|
|
4b61d6e875 | ||
|
|
7d83e350fd | ||
|
|
500ba69548 | ||
|
|
9a422549b1 | ||
|
|
3b48fa455e | ||
|
|
ef013e0639 | ||
|
|
8f8437a88d | ||
|
|
1b091c9b07 | ||
|
|
4801b6f057 | ||
|
|
9078bc2de5 | ||
|
|
b69464dfe9 | ||
|
|
62fa48293a | ||
|
|
b206d0889b | ||
|
|
ee691d81bf | ||
|
|
56876a67cc | ||
|
|
4a0df713aa | ||
|
|
ef801cbfbe | ||
|
|
9378fc88d2 | ||
|
|
f46bfcc3d8 | ||
|
|
ccdb238843 | ||
|
|
f1f61b4e2b | ||
|
|
a44cb745d9 | ||
|
|
f5f5cb023c | ||
|
|
5813e0ce7a | ||
|
|
5a9c2b1e80 | ||
|
|
bda34fdb3b | ||
|
|
426b677eb8 | ||
|
|
67c7e9fd86 | ||
|
|
d8028a8632 | ||
|
|
374743d022 | ||
|
|
cd98ea5008 | ||
|
|
dbda0ed98a | ||
|
|
f5e0ead01c | ||
|
|
44818701bc | ||
|
|
e0f7387dff | ||
|
|
d440a01792 | ||
|
|
665c84ee42 | ||
|
|
e0de96eb4c | ||
|
|
c6ef276811 | ||
|
|
1701aaf78c | ||
|
|
122daa4bfb | ||
|
|
561a9e5275 | ||
|
|
de2453fce9 | ||
|
|
d59d40c118 | ||
|
|
3469df001f | ||
|
|
0d8cfa3031 | ||
|
|
0289586880 | ||
|
|
e46427c7fc | ||
|
|
3ea59d9a8e | ||
|
|
e85dfc6adf | ||
|
|
d0703b78fa | ||
|
|
432e6adf3e | ||
|
|
a057754035 | ||
|
|
0348ace253 | ||
|
|
c5e38203eb | ||
|
|
9ac31d0233 | ||
|
|
9d8d1cd69d | ||
|
|
07a0381f8b | ||
|
|
f841459004 | ||
|
|
78a26fc139 | ||
|
|
9f6628445e | ||
|
|
fa017b5977 | ||
|
|
58f4a970f2 | ||
|
|
021aa8faed | ||
|
|
83f6e037d6 | ||
|
|
baf153434d | ||
|
|
d481bd7993 | ||
|
|
e859c0a6ef | ||
|
|
59a39e66b1 | ||
|
|
fd5ac69a35 | ||
|
|
a940703ae1 | ||
|
|
350729cde8 | ||
|
|
2e14cd6d66 | ||
|
|
f703524f04 | ||
|
|
aa4435c775 | ||
|
|
31a2e368cc | ||
|
|
97e284e65e | ||
|
|
a6baab92f3 | ||
|
|
7c76e0c3ee | ||
|
|
591a4fcf8e | ||
|
|
71dac85600 | ||
|
|
ad90ddd327 | ||
|
|
03f457f3d0 | ||
|
|
a878256367 | ||
|
|
553f78ed55 | ||
|
|
1bc7d2237e | ||
|
|
132222013b | ||
|
|
2008fb552a | ||
|
|
236c034c62 | ||
|
|
f87baf08d3 | ||
|
|
22aa0c2f40 | ||
|
|
88469d4aaa | ||
|
|
1413c5022a | ||
|
|
aa8cdaee22 | ||
|
|
9f6ff54a76 | ||
|
|
e750c747c6 | ||
|
|
9edfe7d9d3 | ||
|
|
c9b7acd22c | ||
|
|
2ba2f0298c | ||
|
|
a24a2b475a | ||
|
|
4005452772 | ||
|
|
d4b7e221f0 | ||
|
|
77c98fd042 | ||
|
|
082872b2f3 | ||
|
|
6253e2e24c | ||
|
|
4216afe62f | ||
|
|
8fec78a5cd | ||
|
|
7ba0a14e97 | ||
|
|
3a442347a5 | ||
|
|
c4f4fd97d6 | ||
|
|
ac0ead1473 | ||
|
|
83cea9475d | ||
|
|
dc6bb7ab1b | ||
|
|
c71f6ba377 | ||
|
|
b1b1ab5350 | ||
|
|
7613b8dbfe | ||
|
|
e4cece6095 | ||
|
|
bcefe8716f | ||
|
|
746b5d8be0 | ||
|
|
f13ecbd9bb | ||
|
|
e839beb73b | ||
|
|
b797cdf91e | ||
|
|
84e4677a94 | ||
|
|
0377a11719 | ||
|
|
d0fa79044a | ||
|
|
f381f8d35a | ||
|
|
92e1e5b893 | ||
|
|
8e8b4dba22 | ||
|
|
767cd55817 | ||
|
|
eb0ef439d6 | ||
|
|
0bf78c0a8a | ||
|
|
12d7e19f32 | ||
|
|
d1c3dd0ee1 | ||
|
|
3dfa99efe1 | ||
|
|
d7bd221a47 | ||
|
|
1b7a3b4a74 | ||
|
|
c8424ed8fd | ||
|
|
150df1ae8e | ||
|
|
5ca9d77176 | ||
|
|
aa89fcc29d | ||
|
|
7ead0de26b | ||
|
|
f22c2690ec | ||
|
|
738bb0eabc | ||
|
|
002a519a17 | ||
|
|
f51128f772 | ||
|
|
d6a0aa7ccf | ||
|
|
ca94a2ddcb | ||
|
|
835ae1217b | ||
|
|
c165969399 | ||
|
|
88c69a06dc | ||
|
|
cd5e7055d2 | ||
|
|
3157593b6b | ||
|
|
c8399a297e | ||
|
|
529cfe2d9a | ||
|
|
50869c6cd2 | ||
|
|
44fcfab9aa |
24
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
24
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Bug Report
|
||||
description: "Create a report to help us improve."
|
||||
labels: [ bug ]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
@@ -9,9 +8,11 @@ body:
|
||||
options:
|
||||
- label: Yes, I'm using the latest major release. Only such installations are supported.
|
||||
required: true
|
||||
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
|
||||
required: true
|
||||
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
||||
required: true
|
||||
- label: Yes, I've included all information below (version, config, etc).
|
||||
- label: Yes, I've included all information below (version, config, log, etc).
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
@@ -30,7 +31,7 @@ body:
|
||||
<details>
|
||||
|
||||
```console
|
||||
$ sing-box --version
|
||||
$ sing-box version
|
||||
# Paste output here
|
||||
```
|
||||
|
||||
@@ -51,4 +52,19 @@ body:
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Server and client log file
|
||||
value: |-
|
||||
<details>
|
||||
|
||||
```console
|
||||
# paste log here
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
|
||||
10
.github/update_dependencies.sh
vendored
10
.github/update_dependencies.sh
vendored
@@ -1,13 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PROJECTS=$(dirname "$0")/../..
|
||||
|
||||
go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
|
||||
go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
|
||||
go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-tun rev-parse HEAD)
|
||||
go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD)
|
||||
go get -x github.com/sagernet/sing-vmess@$(git -C $PROJECTS/sing-vmess rev-parse HEAD)
|
||||
go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)
|
||||
go mod tidy
|
||||
pushd test
|
||||
go mod tidy
|
||||
popd
|
||||
|
||||
17
.github/workflows/debug.yml
vendored
17
.github/workflows/debug.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
@@ -11,6 +12,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- dev-next
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -47,6 +49,7 @@ jobs:
|
||||
go mod init build
|
||||
go get -v github.com/sagernet/sing-box@$version
|
||||
popd
|
||||
continue-on-error: true
|
||||
- name: Run Test
|
||||
run: |
|
||||
go test -v ./...
|
||||
@@ -160,6 +163,7 @@ jobs:
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GOMIPS: ${{ matrix.gomips }}
|
||||
CGO_ENABLED: 0
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -181,18 +185,9 @@ jobs:
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
VERSION="$(date +%Y%m%d).$(git rev-parse --short HEAD)"
|
||||
BUILDTIME="$(LANG=en_US.UTF-8 date -u)"
|
||||
|
||||
go build -v -trimpath -ldflags '\
|
||||
-X "github.com/sagernet/sing-box/constant.Version=$VERSION" \
|
||||
-X "github.com/sagernet/sing-box/constant.BuildTime=$BUILDTIME" \
|
||||
-s -w -buildid=' ./cmd/sing-box
|
||||
|
||||
echo "::set-output name=VERSION::$VERSION"
|
||||
run: make
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sing-box-${{ matrix.name }}-${{ steps.build.outputs.VERSION }}
|
||||
name: sing-box-${{ matrix.name }}
|
||||
path: sing-box*
|
||||
43
.github/workflows/docker.yml
vendored
Normal file
43
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Build Docker Images
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag version you want to build"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Docker metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ghcr.io/sagernet/sing-box
|
||||
- name: Get tag to build
|
||||
id: tag
|
||||
run: |
|
||||
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||
echo ::set-output name=tag::ghcr.io/sagernet/sing-box:${{ github.ref_name }}
|
||||
else
|
||||
echo ::set-output name=tag::ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}
|
||||
fi
|
||||
- name: Build and release Docker images
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
platforms: linux/386,linux/amd64
|
||||
target: dist
|
||||
tags: ${{ steps.tag.outputs.tag }}
|
||||
push: true
|
||||
2
.github/workflows/mkdocs.yml
vendored
2
.github/workflows/mkdocs.yml
vendored
@@ -14,5 +14,5 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material
|
||||
- run: pip install mkdocs-material mkdocs-static-i18n
|
||||
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||
15
.github/workflows/stale.yml
vendored
Normal file
15
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
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'
|
||||
days-before-stale: 60
|
||||
days-before-close: 5
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@
|
||||
/*.json
|
||||
/*.db
|
||||
/site/
|
||||
/bin/
|
||||
/bin/
|
||||
/dist/
|
||||
@@ -3,18 +3,15 @@ linters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- govet
|
||||
- gci
|
||||
# - gci
|
||||
- staticcheck
|
||||
- paralleltest
|
||||
|
||||
issues:
|
||||
fix: true
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
# gci:
|
||||
# sections:
|
||||
# - standard
|
||||
# - prefix(github.com/sagernet/)
|
||||
# - default
|
||||
staticcheck:
|
||||
go: '1.18'
|
||||
go: '1.19'
|
||||
|
||||
85
.goreleaser.yaml
Normal file
85
.goreleaser.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
project_name: sing-box
|
||||
builds:
|
||||
- main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Commit={{ .ShortCommit }} -s -w -buildid=
|
||||
tags:
|
||||
- with_quic
|
||||
- with_wireguard
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- android_arm64
|
||||
- android_amd64
|
||||
- android_amd64_v3
|
||||
- linux_amd64_v1
|
||||
- linux_amd64_v3
|
||||
- linux_arm64
|
||||
- linux_arm_7
|
||||
- windows_amd64_v1
|
||||
- windows_amd64_v3
|
||||
- windows_386
|
||||
- windows_arm64
|
||||
- darwin_amd64_v1
|
||||
- darwin_amd64_v3
|
||||
- darwin_arm64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
archives:
|
||||
- id: archive
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
nfpms:
|
||||
- id: package
|
||||
package_name: sing-box
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
vendor: sagernet
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||
description: The universal proxy platform.
|
||||
license: GPLv3 or later
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
priority: extra
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
- src: release/config/sing-box.service
|
||||
dst: /etc/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /etc/systemd/system/sing-box@.service
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
source:
|
||||
enabled: false
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
|
||||
checksum:
|
||||
disable: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
|
||||
signs:
|
||||
- artifacts: checksum
|
||||
release:
|
||||
github:
|
||||
owner: SagerNet
|
||||
name: sing-box
|
||||
name_template: '{{ if .IsSnapshot }}{{ nightly }}{{ else }}{{ .Version }}{{ end }}'
|
||||
draft: true
|
||||
mode: replace
|
||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM golang:1.19-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
ARG GOPROXY=""
|
||||
ENV GOPROXY ${GOPROXY}
|
||||
ENV CGO_ENABLED=0
|
||||
RUN set -ex \
|
||||
&& apk add git build-base \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& go build -v -trimpath -tags 'no_gvisor,with_quic,with_wireguard,with_acme' \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
|
||||
./cmd/sing-box
|
||||
FROM alpine AS dist
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||
RUN set -ex \
|
||||
&& apk upgrade \
|
||||
&& apk add bash tzdata ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||
ENTRYPOINT ["sing-box"]
|
||||
77
Makefile
Normal file
77
Makefile
Normal file
@@ -0,0 +1,77 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_quic,with_wireguard,with_clash_api
|
||||
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
|
||||
'-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
|
||||
-w -s -buildid='
|
||||
MAIN = ./cmd/sing-box
|
||||
|
||||
.PHONY: test release
|
||||
|
||||
build:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
|
||||
install:
|
||||
go install $(PARAMS) $(MAIN)
|
||||
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
@gofmt -s -w .
|
||||
@gci write -s "standard,prefix(github.com/sagernet/),default" .
|
||||
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go install -v github.com/daixiang0/gci@v0.4.0
|
||||
|
||||
lint:
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=android golangci-lint run ./...
|
||||
GOOS=windows golangci-lint run ./...
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
|
||||
lint_install:
|
||||
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
proto:
|
||||
@go run ./cmd/internal/protogen
|
||||
@gofumpt -l -w .
|
||||
@gofumpt -l -w .
|
||||
|
||||
proto_install:
|
||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
snapshot:
|
||||
goreleaser release --rm-dist --snapshot
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
||||
rm -r dist
|
||||
|
||||
release:
|
||||
goreleaser release --rm-dist --skip-publish
|
||||
mkdir 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
|
||||
rm -r dist
|
||||
|
||||
release_install:
|
||||
go install -v github.com/goreleaser/goreleaser@latest
|
||||
go install -v github.com/tcnksm/ghr@latest
|
||||
|
||||
test:
|
||||
@go test -v . && \
|
||||
pushd test && \
|
||||
go mod tidy && \
|
||||
go test -v -tags with_quic,with_wireguard,with_grpc . && \
|
||||
popd
|
||||
|
||||
clean:
|
||||
rm -rf bin dist
|
||||
rm -f $(shell go env GOPATH)/sing-box
|
||||
|
||||
update:
|
||||
git fetch
|
||||
git reset FETCH_HEAD --hard
|
||||
git clean -fdx
|
||||
@@ -2,11 +2,13 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-dns"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
@@ -15,9 +17,17 @@ type Inbound interface {
|
||||
Tag() string
|
||||
}
|
||||
|
||||
type InjectableInbound interface {
|
||||
Inbound
|
||||
Network() []string
|
||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
||||
type InboundContext struct {
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion int
|
||||
Network string
|
||||
Source M.Socksaddr
|
||||
Destination M.Socksaddr
|
||||
@@ -28,14 +38,16 @@ type InboundContext struct {
|
||||
|
||||
// cache
|
||||
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
DomainStrategy dns.DomainStrategy
|
||||
SniffEnabled bool
|
||||
SniffOverrideDestination bool
|
||||
DestinationAddresses []netip.Addr
|
||||
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *process.Info
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *process.Info
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
@@ -37,6 +37,7 @@ type Router interface {
|
||||
DefaultMark() int
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
Rules() []Rule
|
||||
SetTrafficController(controller TrafficController)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package adapter
|
||||
|
||||
import "io"
|
||||
|
||||
type Starter interface {
|
||||
Start() error
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
Starter
|
||||
io.Closer
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
17
adapter/v2ray.go
Normal file
17
adapter/v2ray.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type V2RayServerTransport interface {
|
||||
Network() []string
|
||||
Serve(listener net.Listener) error
|
||||
ServePacket(listener net.PacketConn) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type V2RayClientTransport interface {
|
||||
DialContext(ctx context.Context) (net.Conn, error)
|
||||
}
|
||||
45
box.go
45
box.go
@@ -2,8 +2,10 @@ package box
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -60,6 +62,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logWriter = logFile
|
||||
}
|
||||
logFormatter := log.Formatter{
|
||||
BaseTime: createdAt,
|
||||
@@ -135,7 +138,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
}
|
||||
outbounds = append(outbounds, out)
|
||||
}
|
||||
err = router.Initialize(outbounds, func() adapter.Outbound {
|
||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
||||
common.Must(oErr)
|
||||
outbounds = append(outbounds, out)
|
||||
@@ -167,6 +170,37 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
}
|
||||
|
||||
func (s *Box) Start() error {
|
||||
err := s.start()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
for i, out := range s.outbounds {
|
||||
if starter, isStarter := out.(common.Starter); isStarter {
|
||||
err := starter.Start()
|
||||
if err != nil {
|
||||
var tag string
|
||||
if out.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = out.Tag()
|
||||
}
|
||||
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
err := s.router.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -174,10 +208,13 @@ func (s *Box) Start() error {
|
||||
for i, in := range s.inbounds {
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
for g := 0; g < i; g++ {
|
||||
s.inbounds[g].Close()
|
||||
var tag string
|
||||
if in.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = in.Tag()
|
||||
}
|
||||
return err
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
if s.clashServer != nil {
|
||||
|
||||
218
cmd/internal/protogen/main.go
Normal file
218
cmd/internal/protogen/main.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// envFile returns the name of the Go environment configuration file.
|
||||
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||
func envFile() (string, error) {
|
||||
if file := os.Getenv("GOENV"); file != "" {
|
||||
if file == "off" {
|
||||
return "", fmt.Errorf("GOENV=off")
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if dir == "" {
|
||||
return "", fmt.Errorf("missing user-config dir")
|
||||
}
|
||||
return filepath.Join(dir, "go", "env"), nil
|
||||
}
|
||||
|
||||
// GetRuntimeEnv returns the value of runtime environment variable,
|
||||
// that is set by running following command: `go env -w key=value`.
|
||||
func GetRuntimeEnv(key string) (string, error) {
|
||||
file, err := envFile()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if file == "" {
|
||||
return "", fmt.Errorf("missing runtime env file")
|
||||
}
|
||||
var data []byte
|
||||
var runtimeEnv string
|
||||
data, readErr := os.ReadFile(file)
|
||||
if readErr != nil {
|
||||
return "", readErr
|
||||
}
|
||||
envStrings := strings.Split(string(data), "\n")
|
||||
for _, envItem := range envStrings {
|
||||
envItem = strings.TrimSuffix(envItem, "\r")
|
||||
envKeyValue := strings.Split(envItem, "=")
|
||||
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
|
||||
runtimeEnv = strings.TrimSpace(envKeyValue[1])
|
||||
}
|
||||
}
|
||||
return runtimeEnv, nil
|
||||
}
|
||||
|
||||
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
|
||||
func GetGOBIN() string {
|
||||
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
|
||||
GOBIN := os.Getenv("GOBIN")
|
||||
if GOBIN == "" {
|
||||
var err error
|
||||
// The one set by user by running `go env -w GOBIN=/path`
|
||||
GOBIN, err = GetRuntimeEnv("GOBIN")
|
||||
if err != nil {
|
||||
// The default one that Golang uses
|
||||
return filepath.Join(build.Default.GOPATH, "bin")
|
||||
}
|
||||
if GOBIN == "" {
|
||||
return filepath.Join(build.Default.GOPATH, "bin")
|
||||
}
|
||||
return GOBIN
|
||||
}
|
||||
return GOBIN
|
||||
}
|
||||
|
||||
func main() {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("Can not get current working directory.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
GOBIN := GetGOBIN()
|
||||
binPath := os.Getenv("PATH")
|
||||
pathSlice := []string{pwd, GOBIN, binPath}
|
||||
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
|
||||
os.Setenv("PATH", binPath)
|
||||
|
||||
suffix := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
suffix = ".exe"
|
||||
}
|
||||
|
||||
protoc := "protoc"
|
||||
|
||||
if linkPath, err := os.Readlink(protoc); err == nil {
|
||||
protoc = linkPath
|
||||
}
|
||||
|
||||
protoFilesMap := make(map[string][]string)
|
||||
walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
filename := filepath.Base(path)
|
||||
if strings.HasSuffix(filename, ".proto") &&
|
||||
filename != "typed_message.proto" &&
|
||||
filename != "descriptor.proto" {
|
||||
protoFilesMap[dir] = append(protoFilesMap[dir], path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
fmt.Println(walkErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, files := range protoFilesMap {
|
||||
for _, relProtoFile := range files {
|
||||
args := []string{
|
||||
"-I", ".",
|
||||
"--go_out", pwd,
|
||||
"--go_opt", "paths=source_relative",
|
||||
"--go-grpc_out", pwd,
|
||||
"--go-grpc_opt", "paths=source_relative",
|
||||
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix),
|
||||
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix),
|
||||
}
|
||||
args = append(args, relProtoFile)
|
||||
cmd := exec.Command(protoc, args...)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
output, cmdErr := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
if cmdErr != nil {
|
||||
fmt.Println(cmdErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalizeWalkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
filename := filepath.Base(path)
|
||||
if strings.HasSuffix(filename, ".pb.go") &&
|
||||
path != "config.pb.go" {
|
||||
if err := NormalizeGeneratedProtoFile(path); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if normalizeWalkErr != nil {
|
||||
fmt.Println(normalizeWalkErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeGeneratedProtoFile(path string) error {
|
||||
fd, err := os.OpenFile(path, os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fd.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out := bytes.NewBuffer(nil)
|
||||
scanner := bufio.NewScanner(fd)
|
||||
valid := false
|
||||
for scanner.Scan() {
|
||||
if !valid && !strings.HasPrefix(scanner.Text(), "package ") {
|
||||
continue
|
||||
}
|
||||
valid = true
|
||||
out.Write(scanner.Bytes())
|
||||
out.Write([]byte("\n"))
|
||||
}
|
||||
_, err = fd.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fd.Truncate(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(fd, bytes.NewReader(out.Bytes()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2,12 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -15,24 +12,26 @@ import (
|
||||
var commandCheck = &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Check configuration",
|
||||
Run: checkConfiguration,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := check()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
|
||||
func checkConfiguration(cmd *cobra.Command, args []string) {
|
||||
configContent, err := os.ReadFile(configPath)
|
||||
func init() {
|
||||
mainCommand.AddCommand(commandCheck)
|
||||
}
|
||||
|
||||
func check() error {
|
||||
options, err := readConfig()
|
||||
if err != nil {
|
||||
log.Fatal("read config: ", err)
|
||||
}
|
||||
var options option.Options
|
||||
err = json.Unmarshal(configContent, &options)
|
||||
if err != nil {
|
||||
log.Fatal("decode config: ", err)
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_, err = box.New(ctx, options)
|
||||
if err != nil {
|
||||
log.Fatal("create service: ", err)
|
||||
}
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -17,47 +18,54 @@ var commandFormatFlagWrite bool
|
||||
var commandFormat = &cobra.Command{
|
||||
Use: "format",
|
||||
Short: "Format configuration",
|
||||
Run: formatConfiguration,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := format()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandFormat.Flags().BoolVarP(&commandFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
|
||||
mainCommand.AddCommand(commandFormat)
|
||||
}
|
||||
|
||||
func formatConfiguration(cmd *cobra.Command, args []string) {
|
||||
func format() error {
|
||||
configContent, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.Fatal("read config: ", err)
|
||||
return E.Cause(err, "read config")
|
||||
}
|
||||
var options option.Options
|
||||
err = json.Unmarshal(configContent, &options)
|
||||
err = options.UnmarshalJSON(configContent)
|
||||
if err != nil {
|
||||
log.Fatal("decode config: ", err)
|
||||
return E.Cause(err, "decode config")
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(options)
|
||||
if err != nil {
|
||||
log.Fatal("encode config: ", err)
|
||||
return E.Cause(err, "encode config")
|
||||
}
|
||||
if !commandFormatFlagWrite {
|
||||
os.Stdout.WriteString(buffer.String() + "\n")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(configContent, buffer.Bytes()) {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
output, err := os.Create(configPath)
|
||||
if err != nil {
|
||||
log.Fatal("open output: ", err)
|
||||
return E.Cause(err, "open output")
|
||||
}
|
||||
_, err = output.Write(buffer.Bytes())
|
||||
output.Close()
|
||||
if err != nil {
|
||||
log.Fatal("write output: ", err)
|
||||
return E.Cause(err, "write output")
|
||||
}
|
||||
outputPath, _ := filepath.Abs(configPath)
|
||||
os.Stderr.WriteString(outputPath + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
runtimeDebug "runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -20,25 +19,43 @@ import (
|
||||
var commandRun = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run service",
|
||||
Run: run,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
err := run0()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
func init() {
|
||||
mainCommand.AddCommand(commandRun)
|
||||
}
|
||||
|
||||
func readConfig() (option.Options, error) {
|
||||
var (
|
||||
configContent []byte
|
||||
err error
|
||||
)
|
||||
if configPath == "stdin" {
|
||||
configContent, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
configContent, err = os.ReadFile(configPath)
|
||||
}
|
||||
}
|
||||
|
||||
func run0() error {
|
||||
configContent, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read config")
|
||||
return option.Options{}, E.Cause(err, "read config")
|
||||
}
|
||||
var options option.Options
|
||||
err = json.Unmarshal(configContent, &options)
|
||||
err = options.UnmarshalJSON(configContent)
|
||||
if err != nil {
|
||||
return E.Cause(err, "decode config")
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func create() (*box.Box, context.CancelFunc, error) {
|
||||
options, err := readConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if disableColor {
|
||||
if options.Log == nil {
|
||||
@@ -50,23 +67,40 @@ func run0() error {
|
||||
instance, err := box.New(ctx, options)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return E.Cause(err, "create service")
|
||||
return nil, nil, E.Cause(err, "create service")
|
||||
}
|
||||
err = instance.Start()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return E.Cause(err, "start service")
|
||||
return nil, nil, E.Cause(err, "start service")
|
||||
}
|
||||
if debug.Enabled {
|
||||
http.HandleFunc("/debug/close", func(writer http.ResponseWriter, request *http.Request) {
|
||||
return instance, cancel, nil
|
||||
}
|
||||
|
||||
func run() error {
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
for {
|
||||
instance, cancel, err := create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtimeDebug.FreeOSMemory()
|
||||
for {
|
||||
osSignal := <-osSignals
|
||||
if osSignal == syscall.SIGHUP {
|
||||
err = check()
|
||||
if err != nil {
|
||||
log.Error(E.Cause(err, "reload service"))
|
||||
continue
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
instance.Close()
|
||||
})
|
||||
if osSignal != syscall.SIGHUP {
|
||||
return nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
|
||||
<-osSignals
|
||||
cancel()
|
||||
instance.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,11 +17,38 @@ var commandVersion = &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
|
||||
func printVersion(cmd *cobra.Command, args []string) {
|
||||
os.Stderr.WriteString(F.ToString("sing-box version ", C.Version, " (", runtime.Version(), ", ", runtime.GOOS, "/", runtime.GOARCH, ", CGO "))
|
||||
if C.CGO_ENABLED {
|
||||
os.Stderr.WriteString("enabled)\n")
|
||||
} else {
|
||||
os.Stderr.WriteString("disabled)\n")
|
||||
}
|
||||
var nameOnly bool
|
||||
|
||||
func init() {
|
||||
commandVersion.Flags().BoolVarP(&nameOnly, "name", "n", false, "print version name only")
|
||||
mainCommand.AddCommand(commandVersion)
|
||||
}
|
||||
|
||||
func printVersion(cmd *cobra.Command, args []string) {
|
||||
var version string
|
||||
if !nameOnly {
|
||||
version = "sing-box "
|
||||
}
|
||||
version += F.ToString(C.Version)
|
||||
if C.Commit != "" {
|
||||
version += "." + C.Commit
|
||||
}
|
||||
if !nameOnly {
|
||||
version += " ("
|
||||
version += runtime.Version()
|
||||
version += ", "
|
||||
version += runtime.GOOS
|
||||
version += "/"
|
||||
version += runtime.GOARCH
|
||||
version += ", "
|
||||
version += "CGO "
|
||||
if C.CGO_ENABLED {
|
||||
version += "enabled"
|
||||
} else {
|
||||
version += "disabled"
|
||||
}
|
||||
version += ")"
|
||||
}
|
||||
version += "\n"
|
||||
os.Stdout.WriteString(version)
|
||||
}
|
||||
|
||||
@@ -23,11 +23,6 @@ func init() {
|
||||
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||
|
||||
mainCommand.AddCommand(commandRun)
|
||||
mainCommand.AddCommand(commandCheck)
|
||||
mainCommand.AddCommand(commandFormat)
|
||||
mainCommand.AddCommand(commandVersion)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
62
common/baderror/baderror.go
Normal file
62
common/baderror/baderror.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package baderror
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func Contains(err error, msgList ...string) bool {
|
||||
for _, msg := range msgList {
|
||||
if strings.Contains(err.Error(), msg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func WrapH2(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = E.Unwrap(err)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return io.EOF
|
||||
}
|
||||
if Contains(err, "client disconnected", "body closed by handler") {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func WrapGRPC(err error) error {
|
||||
// grpc uses stupid internal error types
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if Contains(err, "EOF") {
|
||||
return io.EOF
|
||||
}
|
||||
if Contains(err, "Canceled") {
|
||||
return context.Canceled
|
||||
}
|
||||
if Contains(err,
|
||||
"the client connection is closing",
|
||||
"server closed the stream without sending trailers") {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func WrapQUIC(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if Contains(err, "canceled with error code 0") {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return err
|
||||
}
|
||||
19
common/debugio/print.go
Normal file
19
common/debugio/print.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package debugio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
func PrintUpstream(obj any) {
|
||||
for obj != nil {
|
||||
fmt.Println(reflect.TypeOf(obj))
|
||||
if u, ok := obj.(common.WithUpstream); !ok {
|
||||
break
|
||||
} else {
|
||||
obj = u.Upstream()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dialer
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -52,8 +54,10 @@ var warnTFOOnUnsupportedPlatform = warning.New(
|
||||
)
|
||||
|
||||
type DefaultDialer struct {
|
||||
tfo.Dialer
|
||||
net.ListenConfig
|
||||
dialer tfo.Dialer
|
||||
udpDialer net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
bindUDPAddr string
|
||||
}
|
||||
|
||||
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
|
||||
@@ -66,14 +70,14 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.AutoDetectInterface() {
|
||||
if C.IsWindows {
|
||||
bindFunc := control.BindToInterfaceIndexFunc(func() int {
|
||||
return router.InterfaceMonitor().DefaultInterfaceIndex()
|
||||
bindFunc := control.BindToInterfaceIndexFunc(func(network, address string) int {
|
||||
return router.InterfaceMonitor().DefaultInterfaceIndex(M.ParseSocksaddr(address).Addr)
|
||||
})
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
|
||||
return router.InterfaceMonitor().DefaultInterfaceName()
|
||||
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)
|
||||
@@ -108,17 +112,39 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
if options.TCPFastOpen {
|
||||
warnTFOOnUnsupportedPlatform.Check()
|
||||
}
|
||||
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener}
|
||||
if !options.UDPFragment {
|
||||
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
||||
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
||||
}
|
||||
var bindUDPAddr string
|
||||
udpDialer := dialer
|
||||
var bindAddress netip.Addr
|
||||
if options.BindAddress != nil {
|
||||
bindAddress = options.BindAddress.Build()
|
||||
}
|
||||
if bindAddress.IsValid() {
|
||||
dialer.LocalAddr = &net.TCPAddr{
|
||||
IP: bindAddress.AsSlice(),
|
||||
}
|
||||
udpDialer.LocalAddr = &net.UDPAddr{
|
||||
IP: bindAddress.AsSlice(),
|
||||
}
|
||||
bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).String()
|
||||
}
|
||||
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) {
|
||||
return d.Dialer.DialContext(ctx, network, address.Unwrap().String())
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
return d.udpDialer.DialContext(ctx, network, address.String())
|
||||
}
|
||||
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return d.ListenConfig.ListenPacket(ctx, N.NetworkUDP, "")
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) Upstream() any {
|
||||
return &d.Dialer
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.bindUDPAddr)
|
||||
}
|
||||
|
||||
@@ -10,15 +10,12 @@ import (
|
||||
)
|
||||
|
||||
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
|
||||
var dialer N.Dialer
|
||||
if options.Detour == "" {
|
||||
return NewDefault(router, options)
|
||||
dialer = NewDefault(router, options)
|
||||
} else {
|
||||
return NewDetour(router, options.Detour)
|
||||
dialer = NewDetour(router, options.Detour)
|
||||
}
|
||||
}
|
||||
|
||||
func NewOutbound(router adapter.Router, options option.OutboundDialerOptions) N.Dialer {
|
||||
dialer := New(router, options.DialerOptions)
|
||||
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
||||
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
||||
|
||||
@@ -51,7 +51,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.IsFqdn() || destination.Fqdn == "" {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
|
||||
@@ -20,20 +20,19 @@ type TLSDialer struct {
|
||||
config *tls.Config
|
||||
}
|
||||
|
||||
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||
func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) {
|
||||
if !options.Enabled {
|
||||
return dialer, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
} else if serverAddress != "" {
|
||||
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||
if _, err := netip.ParseAddr(serverName); err == nil {
|
||||
serverName = serverAddress
|
||||
}
|
||||
}
|
||||
if serverName == "" && options.Insecure {
|
||||
if serverName == "" && !options.Insecure {
|
||||
return nil, E.New("missing server_name or insecure=true")
|
||||
}
|
||||
|
||||
@@ -105,9 +104,20 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
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,
|
||||
config: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -119,13 +129,17 @@ func (d *TLSDialer) DialContext(ctx context.Context, network string, destination
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConn := tls.Client(conn, d.config)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
return tlsConn, 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
|
||||
}
|
||||
|
||||
@@ -20,13 +20,11 @@ func Write(writer io.Writer, domains map[string][]Item) error {
|
||||
for _, code := range keys {
|
||||
index[code] = content.Len()
|
||||
for _, domain := range domains[code] {
|
||||
err := rw.WriteByte(content, domain.Type)
|
||||
content.WriteByte(domain.Type)
|
||||
err := rw.WriteVString(content, domain.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = rw.WriteVString(content, domain.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
128
common/json/comment.go
Normal file
128
common/json/comment.go
Normal file
@@ -0,0 +1,128 @@
|
||||
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
|
||||
}
|
||||
@@ -4,18 +4,35 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type Searcher interface {
|
||||
FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error)
|
||||
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error)
|
||||
}
|
||||
|
||||
var ErrNotFound = E.New("process not found")
|
||||
|
||||
type Config struct {
|
||||
Logger log.ContextLogger
|
||||
PackageManager tun.PackageManager
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
ProcessPath string
|
||||
PackageName string
|
||||
User string
|
||||
UserId int32
|
||||
}
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,170 +2,37 @@ package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
var _ Searcher = (*androidSearcher)(nil)
|
||||
|
||||
type androidSearcher struct {
|
||||
logger log.ContextLogger
|
||||
watcher *fsnotify.Watcher
|
||||
userMap map[string]int32
|
||||
packageMap map[int32]string
|
||||
sharedUserMap map[int32]string
|
||||
packageManager tun.PackageManager
|
||||
}
|
||||
|
||||
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
|
||||
return &androidSearcher{logger: logger}, nil
|
||||
func NewSearcher(config Config) (Searcher, error) {
|
||||
return &androidSearcher{config.PackageManager}, nil
|
||||
}
|
||||
|
||||
func (s *androidSearcher) Start() error {
|
||||
err := s.updatePackages()
|
||||
if err != nil {
|
||||
return E.Cause(err, "read packages list")
|
||||
}
|
||||
err = s.startWatcher()
|
||||
if err != nil {
|
||||
s.logger.Warn("create fsnotify watcher: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *androidSearcher) startWatcher() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Add("/data/system/packages.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.watcher = watcher
|
||||
go s.loopUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *androidSearcher) loopUpdate() {
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-s.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := s.updatePackages()
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "update packages list"))
|
||||
}
|
||||
case err, ok := <-s.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.logger.Error(E.Cause(err, "fsnotify error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *androidSearcher) Close() error {
|
||||
return common.Close(common.PtrOrNil(s.watcher))
|
||||
}
|
||||
|
||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
_, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
|
||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
_, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sharedUser, loaded := s.sharedUserMap[uid]; loaded {
|
||||
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
||||
return &Info{
|
||||
UserId: uid,
|
||||
PackageName: sharedUser,
|
||||
UserId: int32(uid),
|
||||
PackageName: sharedPackage,
|
||||
}, nil
|
||||
}
|
||||
if packageName, loaded := s.packageMap[uid]; loaded {
|
||||
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
||||
return &Info{
|
||||
UserId: uid,
|
||||
UserId: int32(uid),
|
||||
PackageName: packageName,
|
||||
}, nil
|
||||
}
|
||||
return &Info{UserId: uid}, nil
|
||||
}
|
||||
|
||||
func (s *androidSearcher) updatePackages() error {
|
||||
userMap := make(map[string]int32)
|
||||
packageMap := make(map[int32]string)
|
||||
sharedUserMap := make(map[int32]string)
|
||||
packagesData, err := os.Open("/data/system/packages.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := xml.NewDecoder(packagesData)
|
||||
var token xml.Token
|
||||
for {
|
||||
token, err = decoder.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
element, isStart := token.(xml.StartElement)
|
||||
if !isStart {
|
||||
continue
|
||||
}
|
||||
|
||||
switch element.Name.Local {
|
||||
case "package":
|
||||
var name string
|
||||
var userID int64
|
||||
for _, attr := range element.Attr {
|
||||
switch attr.Name.Local {
|
||||
case "name":
|
||||
name = attr.Value
|
||||
case "userId", "sharedUserId":
|
||||
userID, err = strconv.ParseInt(attr.Value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if userID == 0 && name == "" {
|
||||
continue
|
||||
}
|
||||
userMap[name] = int32(userID)
|
||||
packageMap[int32(userID)] = name
|
||||
case "shared-user":
|
||||
var name string
|
||||
var userID int64
|
||||
for _, attr := range element.Attr {
|
||||
switch attr.Name.Local {
|
||||
case "name":
|
||||
name = attr.Value
|
||||
case "userId":
|
||||
userID, err = strconv.ParseInt(attr.Value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packageMap[int32(userID)] = name
|
||||
}
|
||||
}
|
||||
if userID == 0 && name == "" {
|
||||
continue
|
||||
}
|
||||
sharedUserMap[int32(userID)] = name
|
||||
}
|
||||
}
|
||||
s.logger.Info("updated packages list: ", len(packageMap), " packages, ", len(sharedUserMap), " shared users")
|
||||
s.userMap = userMap
|
||||
s.packageMap = packageMap
|
||||
s.sharedUserMap = sharedUserMap
|
||||
return nil
|
||||
return &Info{UserId: int32(uid)}, nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -18,12 +17,12 @@ var _ Searcher = (*darwinSearcher)(nil)
|
||||
|
||||
type darwinSearcher struct{}
|
||||
|
||||
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
|
||||
func NewSearcher(_ Config) (Searcher, error) {
|
||||
return &darwinSearcher{}, nil
|
||||
}
|
||||
|
||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
processName, err := findProcessName(network, srcIP, srcPort)
|
||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ type linuxSearcher struct {
|
||||
logger log.ContextLogger
|
||||
}
|
||||
|
||||
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
|
||||
return &linuxSearcher{logger}, nil
|
||||
func NewSearcher(config Config) (Searcher, error) {
|
||||
return &linuxSearcher{config.Logger}, nil
|
||||
}
|
||||
|
||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
|
||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, src
|
||||
s.logger.DebugContext(ctx, "find process path: ", err)
|
||||
}
|
||||
return &Info{
|
||||
UserId: uid,
|
||||
UserId: int32(uid),
|
||||
ProcessPath: processPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -37,19 +37,9 @@ const (
|
||||
pathProc = "/proc"
|
||||
)
|
||||
|
||||
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (inode int32, uid int32, err error) {
|
||||
for attempts := 0; attempts < 3; attempts++ {
|
||||
inode, uid, err = resolveSocketByNetlink0(network, ip, srcPort)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode int32, uid int32, err error) {
|
||||
var family byte
|
||||
var protocol byte
|
||||
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
|
||||
var family uint8
|
||||
var protocol uint8
|
||||
|
||||
switch network {
|
||||
case N.NetworkTCP:
|
||||
@@ -60,13 +50,13 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
|
||||
return 0, 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
if ip.Is4() {
|
||||
if source.Addr().Is4() {
|
||||
family = syscall.AF_INET
|
||||
} else {
|
||||
family = syscall.AF_INET6
|
||||
}
|
||||
|
||||
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
|
||||
req := packSocketDiagRequest(family, protocol, source)
|
||||
|
||||
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
|
||||
if err != nil {
|
||||
@@ -77,16 +67,18 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
|
||||
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
|
||||
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
|
||||
|
||||
if err = syscall.Connect(socket, &syscall.SockaddrNetlink{
|
||||
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
|
||||
Family: syscall.AF_NETLINK,
|
||||
Pad: 0,
|
||||
Pid: 0,
|
||||
Groups: 0,
|
||||
}); err != nil {
|
||||
return 0, 0, err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = syscall.Write(socket, req); err != nil {
|
||||
_, err = syscall.Write(socket, req)
|
||||
if err != nil {
|
||||
return 0, 0, E.Cause(err, "write netlink request")
|
||||
}
|
||||
|
||||
@@ -115,15 +107,12 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
|
||||
}
|
||||
|
||||
inode, uid = unpackSocketDiagResponse(&messages[0])
|
||||
if inode < 0 || uid < 0 {
|
||||
return 0, 0, E.New("invalid inode(", inode, ") or uid(", uid, ")")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort uint16) []byte {
|
||||
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
|
||||
s := make([]byte, 16)
|
||||
copy(s, source.AsSlice())
|
||||
copy(s, source.Addr().AsSlice())
|
||||
|
||||
buf := make([]byte, sizeOfSocketDiagRequest)
|
||||
|
||||
@@ -139,7 +128,7 @@ func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort
|
||||
buf[19] = 0
|
||||
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[24:26], sourcePort)
|
||||
binary.BigEndian.PutUint16(buf[24:26], source.Port())
|
||||
binary.BigEndian.PutUint16(buf[26:28], 0)
|
||||
|
||||
copy(buf[28:44], s)
|
||||
@@ -151,20 +140,20 @@ func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort
|
||||
return buf
|
||||
}
|
||||
|
||||
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
|
||||
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
|
||||
if len(msg.Data) < 72 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
data := msg.Data
|
||||
|
||||
uid = int32(nativeEndian.Uint32(data[64:68]))
|
||||
inode = int32(nativeEndian.Uint32(data[68:72]))
|
||||
uid = nativeEndian.Uint32(data[64:68])
|
||||
inode = nativeEndian.Uint32(data[68:72])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
|
||||
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
|
||||
files, err := os.ReadDir(pathProc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -182,7 +171,7 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
|
||||
if info.Sys().(*syscall.Stat_t).Uid != uid {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@ package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
|
||||
func NewSearcher(_ Config) (Searcher, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -19,7 +18,7 @@ var _ Searcher = (*windowsSearcher)(nil)
|
||||
|
||||
type windowsSearcher struct{}
|
||||
|
||||
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
|
||||
func NewSearcher(_ Config) (Searcher, error) {
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "init win32 api")
|
||||
@@ -64,8 +63,8 @@ func initWin32API() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
processName, err := findProcessName(network, srcIP, srcPort)
|
||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build cgo && linux && !android
|
||||
//go:build linux && !android
|
||||
|
||||
package process
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
info, err := searcher.FindProcessInfo(ctx, network, srcIP, srcPort)
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(cgo && linux && !android)
|
||||
//go:build !linux || android
|
||||
|
||||
package process
|
||||
|
||||
@@ -7,6 +7,6 @@ import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
|
||||
return searcher.FindProcessInfo(ctx, network, srcIP, srcPort)
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
return searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
}
|
||||
|
||||
50
common/proxyproto/dialer.go
Normal file
50
common/proxyproto/dialer.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
var _ N.Dialer = (*Dialer)(nil)
|
||||
|
||||
type Dialer struct {
|
||||
N.Dialer
|
||||
}
|
||||
|
||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
conn, err := d.Dialer.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var source M.Socksaddr
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata != nil {
|
||||
source = metadata.Source
|
||||
}
|
||||
if !source.IsValid() {
|
||||
source = M.SocksaddrFromNet(conn.LocalAddr())
|
||||
}
|
||||
if destination.Addr.Is6() {
|
||||
source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
|
||||
}
|
||||
h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr())
|
||||
_, err = h.WriteTo(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, E.Cause(err, "write proxy protocol header")
|
||||
}
|
||||
return conn, nil
|
||||
default:
|
||||
return d.Dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
}
|
||||
44
common/proxyproto/listener.go
Normal file
44
common/proxyproto/listener.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
net.Listener
|
||||
AcceptNoHeader bool
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufReader := std_bufio.NewReader(conn)
|
||||
header, err := proxyproto.Read(bufReader)
|
||||
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||
return nil, err
|
||||
}
|
||||
if bufReader.Buffered() > 0 {
|
||||
cache := buf.NewSize(bufReader.Buffered())
|
||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn = bufio.NewCachedConn(conn, cache)
|
||||
}
|
||||
if header != nil {
|
||||
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
||||
Source: M.SocksaddrFromNet(header.SourceAddr),
|
||||
Destination: M.SocksaddrFromNet(header.DestinationAddr),
|
||||
}}, nil
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
64
common/redir/redir_darwin.go
Normal file
64
common/redir/redir_darwin.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
PF_OUT = 0x2
|
||||
DIOCNATLOOK = 0xc0544417
|
||||
)
|
||||
|
||||
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
|
||||
fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
nl := struct {
|
||||
saddr, daddr, rsaddr, rdaddr [16]byte
|
||||
sxport, dxport, rsxport, rdxport [4]byte
|
||||
af, proto, protoVariant, direction uint8
|
||||
}{
|
||||
af: syscall.AF_INET,
|
||||
proto: syscall.IPPROTO_TCP,
|
||||
direction: PF_OUT,
|
||||
}
|
||||
la := conn.LocalAddr().(*net.TCPAddr)
|
||||
ra := conn.RemoteAddr().(*net.TCPAddr)
|
||||
raIP, laIP := ra.IP, la.IP
|
||||
raPort, laPort := ra.Port, la.Port
|
||||
switch {
|
||||
case raIP.To4() != nil:
|
||||
copy(nl.saddr[:net.IPv4len], raIP.To4())
|
||||
copy(nl.daddr[:net.IPv4len], laIP.To4())
|
||||
nl.af = syscall.AF_INET
|
||||
default:
|
||||
copy(nl.saddr[:], raIP.To16())
|
||||
copy(nl.daddr[:], laIP.To16())
|
||||
nl.af = syscall.AF_INET6
|
||||
}
|
||||
nl.sxport[0], nl.sxport[1] = byte(raPort>>8), byte(raPort)
|
||||
nl.dxport[0], nl.dxport[1] = byte(laPort>>8), byte(laPort)
|
||||
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {
|
||||
return netip.AddrPort{}, errno
|
||||
}
|
||||
|
||||
var ip net.IP
|
||||
switch nl.af {
|
||||
case syscall.AF_INET:
|
||||
ip = make(net.IP, net.IPv4len)
|
||||
copy(ip, nl.rdaddr[:net.IPv4len])
|
||||
case syscall.AF_INET6:
|
||||
ip = make(net.IP, net.IPv6len)
|
||||
copy(ip, nl.rdaddr[:])
|
||||
}
|
||||
port := uint16(nl.rdxport[0])<<8 | uint16(nl.rdxport[1])
|
||||
destination = netip.AddrPortFrom(M.AddrFromIP(ip), port)
|
||||
return
|
||||
}
|
||||
@@ -3,35 +3,35 @@ package redir
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
|
||||
rawConn, err := conn.(syscall.Conn).SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
syscallConn, ok := common.Cast[syscall.Conn](conn)
|
||||
if !ok {
|
||||
return netip.AddrPort{}, os.ErrInvalid
|
||||
}
|
||||
var rawFd uintptr
|
||||
err = rawConn.Control(func(fd uintptr) {
|
||||
rawFd = fd
|
||||
err = control.Conn(syscallConn, func(fd uintptr) error {
|
||||
const SO_ORIGINAL_DST = 80
|
||||
if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
|
||||
raw, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3]))
|
||||
} else {
|
||||
raw, err := syscall.GetsockoptIPv6MTUInfo(int(fd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
const SO_ORIGINAL_DST = 80
|
||||
if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
|
||||
raw, err := syscall.GetsockoptIPv6Mreq(int(rawFd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil
|
||||
} else {
|
||||
raw, err := syscall.GetsockoptIPv6MTUInfo(int(rawFd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port), nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package redir
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func runCommand(name string, args ...string) error {
|
||||
command := exec.Command(name, args...)
|
||||
command.Env = os.Environ()
|
||||
command.Stdin = os.Stdin
|
||||
command.Stdout = os.Stderr
|
||||
command.Stderr = os.Stderr
|
||||
return command.Run()
|
||||
}
|
||||
|
||||
func readCommand(name string, args ...string) ([]byte, error) {
|
||||
command := exec.Command(name, args...)
|
||||
command.Env = os.Environ()
|
||||
return command.CombinedOutput()
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
@@ -25,9 +26,9 @@ func init() {
|
||||
|
||||
func runAndroidShell(name string, args ...string) error {
|
||||
if !useRish {
|
||||
return runCommand(name, args...)
|
||||
return common.Exec(name, args...).Attach().Run()
|
||||
} else {
|
||||
return runCommand("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " ")))
|
||||
return common.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
@@ -18,8 +20,8 @@ type systemProxy struct {
|
||||
isMixed bool
|
||||
}
|
||||
|
||||
func (p *systemProxy) update() error {
|
||||
newInterfaceName := p.monitor.DefaultInterfaceName()
|
||||
func (p *systemProxy) update(event int) error {
|
||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||
if p.interfaceName == newInterfaceName {
|
||||
return nil
|
||||
}
|
||||
@@ -32,13 +34,13 @@ func (p *systemProxy) update() error {
|
||||
return err
|
||||
}
|
||||
if p.isMixed {
|
||||
err = runCommand("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
|
||||
err = common.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||
}
|
||||
if err == nil {
|
||||
err = runCommand("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
|
||||
err = common.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||
}
|
||||
if err == nil {
|
||||
err = runCommand("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
|
||||
err = common.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -49,19 +51,19 @@ func (p *systemProxy) unset() error {
|
||||
return err
|
||||
}
|
||||
if p.isMixed {
|
||||
err = runCommand("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off")
|
||||
err = common.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||
}
|
||||
if err == nil {
|
||||
err = runCommand("networksetup", "-setwebproxystate", interfaceDisplayName, "off")
|
||||
err = common.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||
}
|
||||
if err == nil {
|
||||
err = runCommand("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off")
|
||||
err = common.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getInterfaceDisplayName(name string) (string, error) {
|
||||
content, err := readCommand("networksetup", "-listallhardwareports")
|
||||
content, err := common.Exec("networksetup", "-listallhardwareports").Read()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -86,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
|
||||
port: port,
|
||||
isMixed: isMixed,
|
||||
}
|
||||
err := proxy.update()
|
||||
err := proxy.update(tun.EventInterfaceUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ func init() {
|
||||
|
||||
func runAsUser(name string, args ...string) error {
|
||||
if os.Getuid() != 0 {
|
||||
return runCommand(name, args...)
|
||||
return common.Exec(name, args...).Attach().Run()
|
||||
} else if sudoUser != "" {
|
||||
return runCommand("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " ")))
|
||||
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||
} else {
|
||||
return E.New("set system proxy: unable to set as root")
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length > 512 {
|
||||
if length == 0 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
_buffer := buf.StackNewSize(int(length))
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package constant
|
||||
|
||||
type DomainStrategy = uint8
|
||||
|
||||
const (
|
||||
DomainStrategyAsIS DomainStrategy = iota
|
||||
DomainStrategyPreferIPv4
|
||||
DomainStrategyPreferIPv6
|
||||
DomainStrategyUseIPv4
|
||||
DomainStrategyUseIPv6
|
||||
)
|
||||
5
constant/err.go
Normal file
5
constant/err.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package constant
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
var ErrTLSRequired = E.New("TLS required")
|
||||
@@ -20,7 +20,7 @@ const IsIos = goos.IsIos == 1
|
||||
|
||||
const IsJs = goos.IsJs == 1
|
||||
|
||||
const IsLinux = goos.IsLinux == 1
|
||||
const IsLinux = goos.IsLinux == 1 || goos.IsAndroid == 1
|
||||
|
||||
const IsNacl = goos.IsNacl == 1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build unix
|
||||
//go:build unix || linux
|
||||
|
||||
package constant
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
resourcePaths = append(resourcePaths, "/etc/config")
|
||||
resourcePaths = append(resourcePaths, "/etc")
|
||||
resourcePaths = append(resourcePaths, "/usr/share")
|
||||
resourcePaths = append(resourcePaths, "/usr/local/etc/config")
|
||||
resourcePaths = append(resourcePaths, "/usr/local/etc")
|
||||
resourcePaths = append(resourcePaths, "/usr/local/share")
|
||||
if homeDir := os.Getenv("HOME"); homeDir != "" {
|
||||
resourcePaths = append(resourcePaths, homeDir+"/.local/share")
|
||||
|
||||
@@ -14,6 +14,11 @@ const (
|
||||
TypeVMess = "vmess"
|
||||
TypeTrojan = "trojan"
|
||||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeHysteria = "hysteria"
|
||||
TypeTor = "tor"
|
||||
TypeSSH = "ssh"
|
||||
TypeShadowTLS = "shadowtls"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
|
||||
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`)
|
||||
|
||||
8
constant/v2ray.go
Normal file
8
constant/v2ray.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
V2RayTransportTypeHTTP = "http"
|
||||
V2RayTransportTypeWebsocket = "ws"
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
Version = "nightly"
|
||||
BuildTime = "unknown"
|
||||
Version = "1.1-beta1"
|
||||
Commit = ""
|
||||
)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Benchmark
|
||||
|
||||
## Shadowsocks
|
||||
|
||||
| / | none | aes-128-gcm | 2022-blake3-aes-128-gcm |
|
||||
|------------------------------------|:-----------:|:-----------:|:-----------------------:|
|
||||
| v2ray-core (5.0.7) | 13.0 Gbps | 5.02 Gbps | / |
|
||||
| shadowsocks-rust (v1.15.0-alpha.5) | 10.7 Gbps | / | 9.36 Gbps |
|
||||
| sing-box | 29.0 Gbps | / | 11.8 Gbps |
|
||||
@@ -1,19 +1,195 @@
|
||||
#### 2022/08/12
|
||||
#### 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
|
||||
|
||||
* Fix match 4in6 address in ip_cidr
|
||||
* Fix clash api log level format error
|
||||
* Fix clash api unknown proxy type
|
||||
|
||||
#### 1.0
|
||||
|
||||
* Fix wireguard reconnect
|
||||
* Fix naive inbound
|
||||
* Fix json format error message
|
||||
* Fix processing vmess termination signal
|
||||
* Fix hysteria stream error
|
||||
* Fix listener close when proxyproto failed
|
||||
|
||||
#### 1.0-rc1
|
||||
|
||||
* Fix write log timestamp
|
||||
* Fix write zero
|
||||
* Fix dial parallel in direct outbound
|
||||
* Fix write trojan udp
|
||||
* Fix DNS routing
|
||||
* Add attribute support for geosite
|
||||
* Update documentation for [Dial Fields](/configuration/shared/dial)
|
||||
|
||||
#### 1.0-beta3
|
||||
|
||||
* Add [chained inbound](/configuration/shared/listen#detour) support
|
||||
* Add process_path rule item
|
||||
* Add macOS redirect support
|
||||
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls)
|
||||
and [Examples](/examples/shadowtls)
|
||||
* Fix search android package in non-owner users
|
||||
* Fix socksaddr type condition
|
||||
* Fix smux session status
|
||||
* Refactor inbound and outbound documentation
|
||||
* Minor fixes
|
||||
|
||||
#### 1.0-beta2
|
||||
|
||||
* Add strict_route option for [Tun inbound](/configuration/inbound/tun#strict_route)
|
||||
* Add packetaddr support for [VMess outbound](/configuration/outbound/vmess#packet_addr)
|
||||
* Add better performing alternative gRPC implementation
|
||||
* Add [docker image](https://github.com/SagerNet/sing-box/pkgs/container/sing-box)
|
||||
* Fix sniff override destination
|
||||
|
||||
#### 1.0-beta1
|
||||
|
||||
* Initial release
|
||||
|
||||
##### 2022/08/26
|
||||
|
||||
* Fix ipv6 route on linux
|
||||
* Fix read DNS message
|
||||
|
||||
##### 2022/08/25
|
||||
|
||||
* Let vmess use zero instead of auto if TLS enabled
|
||||
* Add trojan fallback for ALPN
|
||||
* Improve ip_cidr rule
|
||||
* Fix format bind_address
|
||||
* Fix http proxy with compressed response
|
||||
* Fix route connections
|
||||
|
||||
##### 2022/08/24
|
||||
|
||||
* Fix naive padding
|
||||
* Fix unix search path
|
||||
* Fix close non-duplex connections
|
||||
* Add ACME EAB support
|
||||
* Fix early close on windows and catch any
|
||||
* Initial zh-CN document translation
|
||||
|
||||
##### 2022/08/23
|
||||
|
||||
* Add [V2Ray Transport](/configuration/shared/v2ray-transport) support for VMess and Trojan
|
||||
* Allow plain http request in Naive inbound (It can now be used with nginx)
|
||||
* Add proxy protocol support
|
||||
* Free memory after start
|
||||
* Parse X-Forward-For in HTTP requests
|
||||
* Handle SIGHUP signal
|
||||
|
||||
##### 2022/08/22
|
||||
|
||||
* Add strategy setting for each [DNS server](/configuration/dns/server)
|
||||
* Add bind address to outbound options
|
||||
|
||||
##### 2022/08/21
|
||||
|
||||
* Add [Tor outbound](/configuration/outbound/tor)
|
||||
* Add [SSH outbound](/configuration/outbound/ssh)
|
||||
|
||||
##### 2022/08/20
|
||||
|
||||
* Attempt to unwrap ip-in-fqdn socksaddr
|
||||
* Fix read packages in android 12
|
||||
* Fix route on some android devices
|
||||
* Improve linux process searcher
|
||||
* Fix write socks5 username password auth request
|
||||
* Skip bind connection with private destination to interface
|
||||
* Add [Trojan connection fallback](/configuration/inbound/trojan#fallback)
|
||||
|
||||
##### 2022/08/19
|
||||
|
||||
* Add Hysteria [Inbound](/configuration/inbound/hysteria) and [Outbund](/configuration/outbound/hysteria)
|
||||
* Add [ACME TLS certificate issuer](/configuration/shared/tls)
|
||||
* Allow read config from stdin (-c stdin)
|
||||
* Update gVisor to 20220815.0
|
||||
|
||||
##### 2022/08/18
|
||||
|
||||
* Fix find process with lwip stack
|
||||
* Fix crash on shadowsocks server
|
||||
* Fix crash on darwin tun
|
||||
* Fix write log to file
|
||||
|
||||
##### 2022/08/17
|
||||
|
||||
* Improve async dns transports
|
||||
|
||||
##### 2022/08/16
|
||||
|
||||
* Add ip_version (route/dns) rule item
|
||||
* Add [WireGuard](/configuration/outbound/wireguard) outbound
|
||||
|
||||
##### 2022/08/15
|
||||
|
||||
* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing.
|
||||
|
||||
##### 2022/08/13
|
||||
|
||||
* Fix dns concurrent write
|
||||
|
||||
##### 2022/08/12
|
||||
|
||||
* Performance improvements
|
||||
* Add UoT option for [Socks](/configuration/outbound/socks) outbound
|
||||
* Add UoT option for [SOCKS](/configuration/outbound/socks) outbound
|
||||
|
||||
#### 2022/08/11
|
||||
##### 2022/08/11
|
||||
|
||||
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds
|
||||
|
||||
#### 2022/08/10
|
||||
##### 2022/08/10
|
||||
|
||||
* Add full-featured [Naive](/configuration/inbound/naive) inbound
|
||||
* Fix default dns server option [#9] by iKirby
|
||||
|
||||
#### 2022/08/09
|
||||
##### 2022/08/09
|
||||
|
||||
No changelog before.
|
||||
|
||||
[#9]: https://github.com/SagerNet/sing-box/pull/9
|
||||
[#9]: https://github.com/SagerNet/sing-box/pull/9
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# DNS
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -33,6 +35,8 @@ Default domain strategy for resolving the domain names.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
Take no effect if `server.strategy` is set.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable dns cache.
|
||||
|
||||
46
docs/configuration/dns/index.zh.md
Normal file
46
docs/configuration/dns/index.zh.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# DNS
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [],
|
||||
"rules": [],
|
||||
"final": "",
|
||||
"strategy": "",
|
||||
"disable_cache": false,
|
||||
"disable_expire": false
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
| 键 | 格式 |
|
||||
|----------|------------------------|
|
||||
| `server` | 一组 [DNS 服务器](./server) |
|
||||
| `rules` | 一组 [DNS 规则](./rule) |
|
||||
|
||||
#### final
|
||||
|
||||
默认 DNS 服务器的标签。
|
||||
|
||||
默认使用第一个服务器。
|
||||
|
||||
#### strategy
|
||||
|
||||
默认解析域名策略。
|
||||
|
||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
如果设置了 `server.strategy`,则不生效。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
禁用 DNS 缓存。
|
||||
|
||||
#### disable_expire
|
||||
|
||||
禁用 DNS 缓存过期。
|
||||
@@ -8,6 +8,7 @@
|
||||
"inbound": [
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -37,7 +38,8 @@
|
||||
"private"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24"
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_port": [
|
||||
12345
|
||||
@@ -59,6 +61,9 @@
|
||||
"process_name": [
|
||||
"curl"
|
||||
],
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
@@ -103,13 +108,19 @@
|
||||
|
||||
#### inbound
|
||||
|
||||
Tags of [inbound](../inbound).
|
||||
Tags of [Inbound](/configuration/inbound).
|
||||
|
||||
#### ip_version
|
||||
|
||||
4 (A DNS query) or 6 (AAAA DNS query).
|
||||
|
||||
Not limited if empty.
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` or `udp`.
|
||||
|
||||
#### user
|
||||
#### auth_user
|
||||
|
||||
Username, see each inbound for details.
|
||||
|
||||
@@ -169,6 +180,14 @@ Match port range.
|
||||
|
||||
Match process name.
|
||||
|
||||
#### process_path
|
||||
|
||||
!!! error ""
|
||||
|
||||
Only supported on Linux, Windows, and macOS.
|
||||
|
||||
Match process path.
|
||||
|
||||
#### package_name
|
||||
|
||||
Match android package name.
|
||||
|
||||
254
docs/configuration/dns/rule.zh.md
Normal file
254
docs/configuration/dns/rule.zh.md
Normal file
@@ -0,0 +1,254 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"inbound": [
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
"userb"
|
||||
],
|
||||
"protocol": [
|
||||
"tls",
|
||||
"http",
|
||||
"quic"
|
||||
],
|
||||
"domain": [
|
||||
"test.com"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".cn"
|
||||
],
|
||||
"domain_keyword": [
|
||||
"test"
|
||||
],
|
||||
"domain_regex": [
|
||||
"^stun\\..+"
|
||||
],
|
||||
"geosite": [
|
||||
"cn"
|
||||
],
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24"
|
||||
],
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
"source_port_range": [
|
||||
"1000:2000",
|
||||
":3000",
|
||||
"4000:"
|
||||
],
|
||||
"port": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"port_range": [
|
||||
"1000:2000",
|
||||
":3000",
|
||||
"4000:"
|
||||
],
|
||||
"process_name": [
|
||||
"curl"
|
||||
],
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
"user": [
|
||||
"sekai"
|
||||
],
|
||||
"user_id": [
|
||||
1000
|
||||
],
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
],
|
||||
"server": "local",
|
||||
"disable_cache": false
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [],
|
||||
"server": "local",
|
||||
"disable_cache": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
### 默认字段
|
||||
|
||||
!!! note ""
|
||||
|
||||
默认规则使用以下匹配逻辑:
|
||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
|
||||
(`source_geoip` || `source_ip_cidr`) &&
|
||||
`other fields`
|
||||
|
||||
#### inbound
|
||||
|
||||
[入站](/zh/configuration/inbound) 标签.
|
||||
|
||||
#### ip_version
|
||||
|
||||
4 (A DNS 查询) 或 6 (AAAA DNS 查询)。
|
||||
|
||||
默认不限制。
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
#### auth_user
|
||||
|
||||
认证用户名,参阅入站设置。
|
||||
|
||||
#### protocol
|
||||
|
||||
探测到的协议, 参阅 [协议探测](/zh/configuration/route/sniff/)。
|
||||
|
||||
#### domain
|
||||
|
||||
匹配完整域名。
|
||||
|
||||
#### domain_suffix
|
||||
|
||||
匹配域名后缀。
|
||||
|
||||
#### domain_keyword
|
||||
|
||||
匹配域名关键字。
|
||||
|
||||
#### domain_regex
|
||||
|
||||
匹配域名正则表达式。
|
||||
|
||||
#### geosite
|
||||
|
||||
匹配 GeoSite。
|
||||
|
||||
#### source_geoip
|
||||
|
||||
匹配源 GeoIP。
|
||||
|
||||
#### source_ip_cidr
|
||||
|
||||
匹配源 IP CIDR。
|
||||
|
||||
#### source_port
|
||||
|
||||
匹配源端口。
|
||||
|
||||
#### source_port_range
|
||||
|
||||
匹配源端口范围。
|
||||
|
||||
#### port
|
||||
|
||||
匹配端口。
|
||||
|
||||
#### port_range
|
||||
|
||||
匹配端口范围。
|
||||
|
||||
#### process_name
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
匹配进程名称。
|
||||
|
||||
#### process_path
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
匹配进程路径。
|
||||
|
||||
#### package_name
|
||||
|
||||
匹配 Android 应用包名。
|
||||
|
||||
#### user
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
匹配用户名。
|
||||
|
||||
#### user_id
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
匹配用户 ID。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
|
||||
#### outbound
|
||||
|
||||
匹配出站。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
#### type
|
||||
|
||||
`logical`
|
||||
|
||||
#### mode
|
||||
|
||||
`and` 或 `or`
|
||||
|
||||
#### rules
|
||||
|
||||
包括的默认规则。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
@@ -9,6 +9,7 @@
|
||||
"address": "tls://dns.google",
|
||||
"address_resolver": "local",
|
||||
"address_strategy": "prefer_ipv4",
|
||||
"strategy": "ipv4_only",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
@@ -42,11 +43,11 @@ The address of the dns server.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
To ensure that system DNS is in effect, rather than go's built-in default resolver, enable CGO at compile time.
|
||||
To ensure that system DNS is in effect, rather than Go's built-in default resolver, enable CGO at compile time.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
QUIC and HTTP3 transport is not included by default, see [Installation](/#Installation).
|
||||
QUIC and HTTP3 transport is not included by default, see [Installation](/#installation).
|
||||
|
||||
!!! info ""
|
||||
|
||||
@@ -59,6 +60,7 @@ The address of the dns server.
|
||||
| `server_failure` | `Server failure` |
|
||||
| `name_error` | `Non-existent domain` |
|
||||
| `not_implemented` | `Not implemented` |
|
||||
| `refused` | `Query refused` |
|
||||
|
||||
#### address_resolver
|
||||
|
||||
@@ -74,6 +76,14 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
`dns.strategy` will be used if empty.
|
||||
|
||||
#### strategy
|
||||
|
||||
Default domain strategy for resolving the domain names.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
Take no effect if override by other settings.
|
||||
|
||||
#### detour
|
||||
|
||||
Tag of an outbound for connecting to the dns server.
|
||||
|
||||
91
docs/configuration/dns/server.zh.md
Normal file
91
docs/configuration/dns/server.zh.md
Normal file
@@ -0,0 +1,91 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://dns.google",
|
||||
"address_resolver": "local",
|
||||
"address_strategy": "prefer_ipv4",
|
||||
"strategy": "ipv4_only",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### tag
|
||||
|
||||
DNS 服务器的标签。
|
||||
|
||||
#### address
|
||||
|
||||
==必填==
|
||||
|
||||
DNS 服务器的地址。
|
||||
|
||||
| 协议 | 格式 |
|
||||
|----------|-----------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
为了确保系统 DNS 生效,而不是 Go 的内置默认解析器,请在编译时启用 CGO。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 QUIC 和 HTTP3 传输层,请参阅 [安装](/zh/#_2)。
|
||||
|
||||
!!! info ""
|
||||
|
||||
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
|
||||
|
||||
| RCode | 描述 |
|
||||
|-------------------|----------|
|
||||
| `success` | `无错误` |
|
||||
| `format_error` | `请求格式错误` |
|
||||
| `server_failure` | `服务器出错` |
|
||||
| `name_error` | `域名不存在` |
|
||||
| `not_implemented` | `功能未实现` |
|
||||
| `refused` | `请求被拒绝` |
|
||||
|
||||
#### address_resolver
|
||||
|
||||
==如果服务器地址包括域名则必须==
|
||||
|
||||
用于解析本 DNS 服务器的域名的另一个 DNS 服务器的标签。
|
||||
|
||||
#### address_strategy
|
||||
|
||||
用于解析本 DNS 服务器的域名的策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
默认使用 `dns.strategy`。
|
||||
|
||||
#### strategy
|
||||
|
||||
默认解析策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
如果被其他设置覆盖则不生效。
|
||||
|
||||
#### detour
|
||||
|
||||
用于连接到 DNS 服务器的出站的标签。
|
||||
|
||||
如果为空,将使用默认出站。
|
||||
@@ -1,3 +1,5 @@
|
||||
# Experimental
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -16,7 +18,7 @@
|
||||
|
||||
!!! error ""
|
||||
|
||||
Clash API is not included by default, see [Installation](/#Installation).
|
||||
Clash API is not included by default, see [Installation](/#installation).
|
||||
|
||||
!!! note ""
|
||||
|
||||
@@ -29,7 +31,7 @@ RESTful web API listening address. Disabled if empty.
|
||||
#### external_ui
|
||||
|
||||
A relative path to the configuration directory or an absolute path to a
|
||||
directory in which you put some static web resource. Clash core will then
|
||||
directory in which you put some static web resource. sing-box will then
|
||||
serve it at `http://{{external-controller}}/ui`.
|
||||
|
||||
#### secret
|
||||
39
docs/configuration/experimental/index.zh.md
Normal file
39
docs/configuration/experimental/index.zh.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 实验性
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "folder",
|
||||
"secret": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Clash API 字段
|
||||
|
||||
!!! error ""
|
||||
|
||||
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
||||
|
||||
!!! note ""
|
||||
|
||||
流量统计和连接管理将禁用 Linux 中的 TCP splice 并降低性能,使用风险自负。
|
||||
|
||||
#### external_controller
|
||||
|
||||
RESTful web API 监听地址。
|
||||
|
||||
#### external_ui
|
||||
|
||||
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
||||
|
||||
#### secret
|
||||
|
||||
RESTful API 的密钥(可选)
|
||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
||||
@@ -4,70 +4,22 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 5353,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
|
||||
"network": "udp",
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53
|
||||
}
|
||||
]
|
||||
"type": "direct",
|
||||
"tag": "direct-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"network": "udp",
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
### Direct Fields
|
||||
### Fields
|
||||
|
||||
#### network
|
||||
|
||||
|
||||
37
docs/configuration/inbound/direct.zh.md
Normal file
37
docs/configuration/inbound/direct.zh.md
Normal file
@@ -0,0 +1,37 @@
|
||||
`direct` 入站是一个隧道服务器。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"network": "udp",
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### network
|
||||
|
||||
监听的网络协议,`tcp` `udp` 之一。
|
||||
|
||||
默认所有。
|
||||
|
||||
#### override_address
|
||||
|
||||
覆盖连接目标地址。
|
||||
|
||||
#### override_port
|
||||
|
||||
覆盖连接目标端口。
|
||||
|
||||
@@ -1,71 +1,38 @@
|
||||
`socks` inbound is a http server.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "http",
|
||||
"tag": "http-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"type": "http",
|
||||
"tag": "http-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 2080,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"set_system_proxy": false
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
]
|
||||
],
|
||||
"tls": {},
|
||||
"set_system_proxy": false
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
### Fields
|
||||
|
||||
Listen address.
|
||||
#### tls
|
||||
|
||||
#### listen_port
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
==Required==
|
||||
#### users
|
||||
|
||||
Listen port.
|
||||
HTTP users.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
No authentication required if empty.
|
||||
|
||||
#### set_system_proxy
|
||||
|
||||
@@ -74,15 +41,3 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
|
||||
Only supported on Linux, Android, Windows, and macOS.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
|
||||
### HTTP Fields
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
||||
|
||||
#### users
|
||||
|
||||
HTTP users.
|
||||
|
||||
No authentication required if empty.
|
||||
43
docs/configuration/inbound/http.zh.md
Normal file
43
docs/configuration/inbound/http.zh.md
Normal file
@@ -0,0 +1,43 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "http",
|
||||
"tag": "http-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"set_system_proxy": false
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### users
|
||||
|
||||
HTTP 用户
|
||||
|
||||
如果为空则不需要验证。
|
||||
|
||||
#### set_system_proxy
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
100
docs/configuration/inbound/hysteria.md
Normal file
100
docs/configuration/inbound/hysteria.md
Normal file
@@ -0,0 +1,100 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hysteria",
|
||||
"tag": "hysteria-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"up": "100 Mbps",
|
||||
"up_mbps": 100,
|
||||
"down": "100 Mbps",
|
||||
"down_mbps": 100,
|
||||
"obfs": "fuck me till the daylight",
|
||||
"auth": "",
|
||||
"auth_str": "password",
|
||||
"recv_window_conn": 0,
|
||||
"recv_window_client": 0,
|
||||
"max_conn_client": 0,
|
||||
"disable_mtu_discovery": false,
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### up, down
|
||||
|
||||
==Required==
|
||||
|
||||
Format: `[Integer] [Unit]` e.g. `100 Mbps, 640 KBps, 2 Gbps`
|
||||
|
||||
Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
|
||||
|
||||
bps (bits per second)
|
||||
Bps (bytes per second)
|
||||
Kbps (kilobits per second)
|
||||
KBps (kilobytes per second)
|
||||
Mbps (megabits per second)
|
||||
MBps (megabytes per second)
|
||||
Gbps (gigabits per second)
|
||||
GBps (gigabytes per second)
|
||||
Tbps (terabits per second)
|
||||
TBps (terabytes per second)
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
==Required==
|
||||
|
||||
`up, down` in Mbps.
|
||||
|
||||
#### obfs
|
||||
|
||||
Obfuscated password.
|
||||
|
||||
#### auth
|
||||
|
||||
Authentication password, in base64.
|
||||
|
||||
#### auth_str
|
||||
|
||||
Authentication password.
|
||||
|
||||
#### recv_window_conn
|
||||
|
||||
The QUIC stream-level flow control window for receiving data.
|
||||
|
||||
`15728640 (15 MB/s)` will be used if empty.
|
||||
|
||||
#### recv_window_client
|
||||
|
||||
The QUIC connection-level flow control window for receiving data.
|
||||
|
||||
`67108864 (64 MB/s)` will be used if empty.
|
||||
|
||||
#### max_conn_client
|
||||
|
||||
The maximum number of QUIC concurrent bidirectional streams that a peer is allowed to open.
|
||||
|
||||
`1024` will be used if empty.
|
||||
|
||||
#### disable_mtu_discovery
|
||||
|
||||
Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
|
||||
|
||||
Force enabled on for systems other than Linux and Windows (according to upstream).
|
||||
|
||||
#### tls
|
||||
|
||||
==Required==
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
100
docs/configuration/inbound/hysteria.zh.md
Normal file
100
docs/configuration/inbound/hysteria.zh.md
Normal file
@@ -0,0 +1,100 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hysteria",
|
||||
"tag": "hysteria-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"up": "100 Mbps",
|
||||
"up_mbps": 100,
|
||||
"down": "100 Mbps",
|
||||
"down_mbps": 100,
|
||||
"obfs": "fuck me till the daylight",
|
||||
"auth": "",
|
||||
"auth_str": "password",
|
||||
"recv_window_conn": 0,
|
||||
"recv_window_client": 0,
|
||||
"max_conn_client": 0,
|
||||
"disable_mtu_discovery": false,
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含被 Hysteria 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### up, down
|
||||
|
||||
==必填==
|
||||
|
||||
格式: `[Integer] [Unit]` 例如: `100 Mbps, 640 KBps, 2 Gbps`
|
||||
|
||||
支持的单位 (大小写敏感, b = bits, B = bytes, 8b=1B):
|
||||
|
||||
bps (bits per second)
|
||||
Bps (bytes per second)
|
||||
Kbps (kilobits per second)
|
||||
KBps (kilobytes per second)
|
||||
Mbps (megabits per second)
|
||||
MBps (megabytes per second)
|
||||
Gbps (gigabits per second)
|
||||
GBps (gigabytes per second)
|
||||
Tbps (terabits per second)
|
||||
TBps (terabytes per second)
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
==必填==
|
||||
|
||||
以 Mbps 为单位的 `up, down`。
|
||||
|
||||
#### obfs
|
||||
|
||||
混淆密码。
|
||||
|
||||
#### auth
|
||||
|
||||
base64 编码的认证密码。
|
||||
|
||||
#### auth_str
|
||||
|
||||
认证密码。
|
||||
|
||||
#### recv_window_conn
|
||||
|
||||
用于接收数据的 QUIC 流级流控制窗口。
|
||||
|
||||
默认 `15728640 (15 MB/s)`。
|
||||
|
||||
#### recv_window_client
|
||||
|
||||
用于接收数据的 QUIC 连接级流控制窗口。
|
||||
|
||||
默认 `67108864 (64 MB/s)`。
|
||||
|
||||
#### max_conn_client
|
||||
|
||||
允许对等点打开的 QUIC 并发双向流的最大数量。
|
||||
|
||||
默认 `1024`。
|
||||
|
||||
#### disable_mtu_discovery
|
||||
|
||||
禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) 字节。
|
||||
|
||||
强制为 Linux 和 Windows 以外的系统启用(根据上游)。
|
||||
|
||||
#### tls
|
||||
|
||||
==必填==
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
@@ -1,3 +1,5 @@
|
||||
# Inbound
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -13,19 +15,20 @@
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|---------------|------------------------------|
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `mixed` | [Mixed](./mixed) |
|
||||
| `socks` | [Socks](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `vmess` | [VMess](./vmess) |
|
||||
| `trojan` | [Trojan](./trojan) |
|
||||
| `naive` | [Naive](./naive) |
|
||||
| `tun` | [Tun](./tun) |
|
||||
| `redirect` | [Redirect](./redirect) |
|
||||
| `tproxy` | [TProxy](./tproxy) |
|
||||
| Type | Format | Injectable |
|
||||
|---------------|------------------------------|------------|
|
||||
| `direct` | [Direct](./direct) | X |
|
||||
| `mixed` | [Mixed](./mixed) | TCP |
|
||||
| `socks` | [SOCKS](./socks) | TCP |
|
||||
| `http` | [HTTP](./http) | TCP |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP |
|
||||
| `vmess` | [VMess](./vmess) | TCP |
|
||||
| `trojan` | [Trojan](./trojan) | TCP |
|
||||
| `naive` | [Naive](./naive) | X |
|
||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||
| `tun` | [Tun](./tun) | X |
|
||||
| `redirect` | [Redirect](./redirect) | X |
|
||||
| `tproxy` | [TProxy](./tproxy) | X |
|
||||
|
||||
#### tag
|
||||
|
||||
|
||||
35
docs/configuration/inbound/index.zh.md
Normal file
35
docs/configuration/inbound/index.zh.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 入站
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
| 类型 | 格式 | 注入支持 |
|
||||
|---------------|------------------------------|------|
|
||||
| `direct` | [Direct](./direct) | X |
|
||||
| `mixed` | [Mixed](./mixed) | TCP |
|
||||
| `socks` | [SOCKS](./socks) | TCP |
|
||||
| `http` | [HTTP](./http) | TCP |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP |
|
||||
| `vmess` | [VMess](./vmess) | TCP |
|
||||
| `trojan` | [Trojan](./trojan) | TCP |
|
||||
| `naive` | [Naive](./naive) | X |
|
||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||
| `tun` | [Tun](./tun) | X |
|
||||
| `redirect` | [Redirect](./redirect) | X |
|
||||
| `tproxy` | [TProxy](./tproxy) | X |
|
||||
|
||||
#### tag
|
||||
|
||||
入站的标签。
|
||||
@@ -4,68 +4,32 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 2080,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
],
|
||||
|
||||
"set_system_proxy": false
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
]
|
||||
],
|
||||
"set_system_proxy": false
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
### Fields
|
||||
|
||||
Listen address.
|
||||
#### users
|
||||
|
||||
#### listen_port
|
||||
SOCKS and HTTP users.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
No authentication required if empty.
|
||||
|
||||
#### set_system_proxy
|
||||
|
||||
@@ -73,12 +37,4 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
|
||||
|
||||
Only supported on Linux, Android, Windows, and macOS.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
|
||||
### Mixed Fields
|
||||
|
||||
#### users
|
||||
|
||||
Socks and HTTP users.
|
||||
|
||||
No authentication required if empty.
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
40
docs/configuration/inbound/mixed.zh.md
Normal file
40
docs/configuration/inbound/mixed.zh.md
Normal file
@@ -0,0 +1,40 @@
|
||||
`mixed` 入站是一个 socks4, socks4a, socks5 和 http 服务器.
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
],
|
||||
"set_system_proxy": false
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
SOCKS 和 HTTP 用户
|
||||
|
||||
如果为空则不需要验证。
|
||||
|
||||
#### set_system_proxy
|
||||
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
@@ -2,80 +2,37 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "naive",
|
||||
"tag": "naive-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 443,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"type": "naive",
|
||||
"tag": "naive-in",
|
||||
"network": "udp",
|
||||
|
||||
"network": "udp",
|
||||
"users": [
|
||||
{
|
||||
"username": "sekai",
|
||||
"password": "password"
|
||||
}
|
||||
],
|
||||
"tls": {}
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "sekai",
|
||||
"password": "password"
|
||||
}
|
||||
]
|
||||
],
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
HTTP3 transport is not included by default, see [Installation](/#Installation).
|
||||
HTTP3 transport is not included by default, see [Installation](/#installation).
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
### Fields
|
||||
|
||||
Listen address.
|
||||
#### network
|
||||
|
||||
#### listen_port
|
||||
Listen network, one of `tcp` `udp`.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
### Naive Fields
|
||||
|
||||
#### tls
|
||||
|
||||
==Required==
|
||||
|
||||
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
||||
Both if empty.
|
||||
|
||||
#### users
|
||||
|
||||
@@ -83,8 +40,6 @@ TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inboun
|
||||
|
||||
Naive users.
|
||||
|
||||
#### network
|
||||
#### tls
|
||||
|
||||
Listen network, one of `tcp` `udp`.
|
||||
|
||||
Both if empty.
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
45
docs/configuration/inbound/naive.zh.md
Normal file
45
docs/configuration/inbound/naive.zh.md
Normal file
@@ -0,0 +1,45 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "naive",
|
||||
"tag": "naive-in",
|
||||
"network": "udp",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "sekai",
|
||||
"password": "password"
|
||||
}
|
||||
],
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#_2)。
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### network
|
||||
|
||||
监听的网络协议,`tcp` `udp` 之一。
|
||||
|
||||
默认所有。
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
Naive 用户。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
@@ -1,54 +1,18 @@
|
||||
`redirect` inbound is a linux redirect server.
|
||||
!!! error ""
|
||||
|
||||
Only supported on Linux and macOS.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "redirect",
|
||||
"tag": "redirect-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 5353,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6"
|
||||
}
|
||||
]
|
||||
"type": "redirect",
|
||||
"tag": "redirect-in",
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
17
docs/configuration/inbound/redirect.zh.md
Normal file
17
docs/configuration/inbound/redirect.zh.md
Normal file
@@ -0,0 +1,17 @@
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux 和 macOS。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "redirect",
|
||||
"tag": "redirect-in",
|
||||
|
||||
... // 监听字段
|
||||
}
|
||||
```
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
@@ -2,22 +2,44 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "shadowsocks",
|
||||
"tag": "ss-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-User Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"users": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"tag": "ss-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 5353,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
"network": "udp",
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Relay Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"destinations": [
|
||||
{
|
||||
"name": "test",
|
||||
"server": "example.com",
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -25,47 +47,9 @@
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
### Shadowsocks Fields
|
||||
### Fields
|
||||
|
||||
#### network
|
||||
|
||||
@@ -99,44 +83,48 @@ Both if empty.
|
||||
| 2022 methods | `openssl rand -base64 <Key Length>` |
|
||||
| other methods | any string |
|
||||
|
||||
### Multi-User Structure
|
||||
### Listen Fields
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
#### listen
|
||||
|
||||
### Relay Structure
|
||||
==Required==
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"destinations": [
|
||||
{
|
||||
"name": "test",
|
||||
"server": "example.com",
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
||||
84
docs/configuration/inbound/shadowsocks.zh.md
Normal file
84
docs/configuration/inbound/shadowsocks.zh.md
Normal file
@@ -0,0 +1,84 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"tag": "ss-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
```
|
||||
|
||||
### 多用户结构
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 中转结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"destinations": [
|
||||
{
|
||||
"name": "test",
|
||||
"server": "example.com",
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
### 字段
|
||||
|
||||
#### network
|
||||
|
||||
监听的网络协议,`tcp` `udp` 之一。
|
||||
|
||||
默认所有。
|
||||
|
||||
#### method
|
||||
|
||||
==必填==
|
||||
|
||||
| 方法 | 密钥长度 |
|
||||
|-------------------------------|------|
|
||||
| 2022-blake3-aes-128-gcm | 16 |
|
||||
| 2022-blake3-aes-256-gcm | 32 |
|
||||
| 2022-blake3-chacha20-poly1305 | 32 |
|
||||
| none | / |
|
||||
| aes-128-gcm | / |
|
||||
| aes-192-gcm | / |
|
||||
| aes-256-gcm | / |
|
||||
| chacha20-ietf-poly1305 | / |
|
||||
| xchacha20-ietf-poly1305 | / |
|
||||
|
||||
#### password
|
||||
|
||||
==必填==
|
||||
|
||||
| 方法 | 密码格式 |
|
||||
|---------------|-------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `openssl rand -base64 <密钥长度>` |
|
||||
| other methods | 任意字符串 |
|
||||
31
docs/configuration/inbound/shadowtls.md
Normal file
31
docs/configuration/inbound/shadowtls.md
Normal file
@@ -0,0 +1,31 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowtls",
|
||||
"tag": "st-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
|
||||
### Fields
|
||||
|
||||
#### handshake
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [dial options](/configuration/shared/dial).
|
||||
|
||||
29
docs/configuration/inbound/shadowtls.zh.md
Normal file
29
docs/configuration/inbound/shadowtls.zh.md
Normal file
@@ -0,0 +1,29 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowtls",
|
||||
"tag": "st-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### handshake
|
||||
|
||||
==必填==
|
||||
|
||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
@@ -4,24 +4,15 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "socks",
|
||||
"tag": "socks-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"type": "socks",
|
||||
"tag": "socks-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 2080,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
]
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -29,46 +20,12 @@
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
### Socks Fields
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
Socks users.
|
||||
SOCKS users.
|
||||
|
||||
No authentication required if empty.
|
||||
No authentication required if empty.
|
||||
|
||||
31
docs/configuration/inbound/socks.zh.md
Normal file
31
docs/configuration/inbound/socks.zh.md
Normal file
@@ -0,0 +1,31 @@
|
||||
`socks` 入站是一个 socks4, socks4a 和 socks5 服务器.
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "socks",
|
||||
"tag": "socks-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
SOCKS 用户
|
||||
|
||||
如果为空则不需要验证。
|
||||
@@ -1,67 +1,28 @@
|
||||
!!! error ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tproxy",
|
||||
"tag": "tproxy-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 5353,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
|
||||
"network": "udp"
|
||||
}
|
||||
]
|
||||
"type": "tproxy",
|
||||
"tag": "tproxy-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"network": "udp"
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
### TProxy Fields
|
||||
### Fields
|
||||
|
||||
#### network
|
||||
|
||||
Listen network, one of `tcp` `udp`.
|
||||
|
||||
Both if empty.
|
||||
Both if empty.
|
||||
|
||||
28
docs/configuration/inbound/tproxy.zh.md
Normal file
28
docs/configuration/inbound/tproxy.zh.md
Normal file
@@ -0,0 +1,28 @@
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tproxy",
|
||||
"tag": "tproxy-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"network": "udp"
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### network
|
||||
|
||||
监听的网络协议,`tcp` `udp` 之一。
|
||||
|
||||
默认所有。
|
||||
@@ -2,74 +2,62 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "trojan",
|
||||
"tag": "trojan-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"type": "trojan",
|
||||
"tag": "trojan-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 2080,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"tls": {}
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"tls": {},
|
||||
"fallback": {
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
},
|
||||
"fallback_for_alpn": {
|
||||
"http/1.1": {
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
### Trojan Fields
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
==Required==
|
||||
|
||||
Trojan users.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### fallback
|
||||
|
||||
!!! error ""
|
||||
|
||||
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
||||
|
||||
Fallback server configuration. Disabled if `fallback` and `fallback_for_alpn` are empty.
|
||||
|
||||
#### fallback_for_alpn
|
||||
|
||||
Fallback server configuration for specified ALPN.
|
||||
|
||||
If not empty, TLS fallback requests with ALPN not in this table will be rejected.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
65
docs/configuration/inbound/trojan.zh.md
Normal file
65
docs/configuration/inbound/trojan.zh.md
Normal file
@@ -0,0 +1,65 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "trojan",
|
||||
"tag": "trojan-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"fallback": {
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
},
|
||||
"fallback_for_alpn": {
|
||||
"http/1.1": {
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
Trojan 用户。
|
||||
|
||||
#### tls
|
||||
|
||||
==如果启用 HTTP3 则必填==
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### fallback
|
||||
|
||||
!!! error ""
|
||||
|
||||
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
|
||||
|
||||
回退服务器配置。如果 `fallback` 和 `fallback_for_alpn` 为空,则禁用回退。
|
||||
|
||||
#### fallback_for_alpn
|
||||
|
||||
为 ALPN 指定回退服务器配置。
|
||||
|
||||
如果不为空,ALPN 不在此列表中的 TLS 回退请求将被拒绝。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
@@ -6,33 +6,53 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/128",
|
||||
"mtu": 1500,
|
||||
"auto_route": true,
|
||||
"endpoint_independent_nat": false,
|
||||
"udp_timeout": 300,
|
||||
"stack": "gvisor",
|
||||
|
||||
"sniff": true,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv4"
|
||||
}
|
||||
]
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/128",
|
||||
"mtu": 9000,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "gvisor",
|
||||
"include_uid": [
|
||||
0
|
||||
],
|
||||
"include_uid_range": [
|
||||
"1000-99999"
|
||||
],
|
||||
"exclude_uid": [
|
||||
1000
|
||||
],
|
||||
"exclude_uid_range": [
|
||||
"1000-99999"
|
||||
],
|
||||
"include_android_user": [
|
||||
0,
|
||||
10
|
||||
],
|
||||
"include_package": [
|
||||
"com.android.chrome"
|
||||
],
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
!!! warning ""
|
||||
|
||||
If tun is running in non-privileged mode, the address and MTU will not be configured automatically, please make sure the settings are accurate.
|
||||
If tun is running in non-privileged mode, addresses and MTU will not be configured automatically, please make sure the settings are accurate.
|
||||
|
||||
### Tun Fields
|
||||
### Fields
|
||||
|
||||
#### interface_name
|
||||
|
||||
@@ -60,8 +80,26 @@ Set the default route to the Tun.
|
||||
|
||||
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
|
||||
|
||||
Enforce strict routing rules in Linux when `auto_route` is enabled:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Route all connections to tun
|
||||
|
||||
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.
|
||||
|
||||
#### 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.
|
||||
|
||||
Performance may degrade slightly, so it is not recommended to enable on when it is not needed.
|
||||
@@ -74,33 +112,57 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
TCP/IP stack.
|
||||
|
||||
| Stack | Upstream | Status |
|
||||
|------------------|-----------------------------------------------------------------------|-------------------|
|
||||
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | recommended |
|
||||
| LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
|
||||
| Stack | Description | Status |
|
||||
|------------------|--------------------------------------------------------------------------------|-------------------|
|
||||
| gVisor (default) | Based on [google/gvisor](https://github.com/google/gvisor) | recommended |
|
||||
| system | Less compatibility and sometimes better performance. | recommended |
|
||||
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
The LWIP stack is not included by default, see [Installation](/#Installation).
|
||||
The LWIP stack is not included by default, see [Installation](/#installation).
|
||||
|
||||
#### include_uid
|
||||
|
||||
!!! error ""
|
||||
|
||||
UID rules are only supported on Linux and require auto_route.
|
||||
|
||||
Limit users in route. Not limited by default.
|
||||
|
||||
#### include_uid_range
|
||||
|
||||
Limit users in route, but in range.
|
||||
|
||||
#### exclude_uid
|
||||
|
||||
Exclude users in route.
|
||||
|
||||
#### exclude_uid_range
|
||||
|
||||
Exclude users in route, but in range.
|
||||
|
||||
#### include_android_user
|
||||
|
||||
!!! error ""
|
||||
|
||||
Android user and package rules are only supported on Android and require auto_route.
|
||||
|
||||
Limit android users in route.
|
||||
|
||||
| Common user | ID |
|
||||
|--------------|-----|
|
||||
| Main | 0 |
|
||||
| Work Profile | 10 |
|
||||
|
||||
#### include_package
|
||||
|
||||
Limit android packages in route.
|
||||
|
||||
#### exclude_package
|
||||
|
||||
Exclude android packages in route.
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
163
docs/configuration/inbound/tun.zh.md
Normal file
163
docs/configuration/inbound/tun.zh.md
Normal file
@@ -0,0 +1,163 @@
|
||||
!!! error ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/128",
|
||||
"mtu": 9000,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "gvisor",
|
||||
"include_uid": [
|
||||
0
|
||||
],
|
||||
"include_uid_range": [
|
||||
"1000-99999"
|
||||
],
|
||||
"exclude_uid": [
|
||||
1000
|
||||
],
|
||||
"exclude_uid_range": [
|
||||
"1000-99999"
|
||||
],
|
||||
"include_android_user": [
|
||||
0,
|
||||
10
|
||||
],
|
||||
"include_package": [
|
||||
"com.android.chrome"
|
||||
],
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
|
||||
... // 监听字段
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
如果 tun 在非特权模式下运行,地址和 MTU 将不会自动配置,请确保设置正确。
|
||||
|
||||
### Tun 字段
|
||||
|
||||
#### interface_name
|
||||
|
||||
虚拟设备名称,默认自动选择。
|
||||
|
||||
#### inet4_address
|
||||
|
||||
==必填==
|
||||
|
||||
tun 接口的 IPv4 前缀。
|
||||
|
||||
#### inet6_address
|
||||
|
||||
tun 接口的 IPv6 前缀。
|
||||
|
||||
#### mtu
|
||||
|
||||
最大传输单元。
|
||||
|
||||
#### auto_route
|
||||
|
||||
设置到 Tun 的默认路由。
|
||||
|
||||
!!! error ""
|
||||
|
||||
为避免流量环回,请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。
|
||||
|
||||
!!! note "与 Android VPN 一起使用"
|
||||
|
||||
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
||||
|
||||
#### strict_route
|
||||
|
||||
在 Linux 中启用 `auto_route` 时执行严格的路由规则。
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
启用独立于端点的 NAT。
|
||||
|
||||
性能可能会略有下降,所以不建议在不需要的时候开启。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
|
||||
#### stack
|
||||
|
||||
TCP/IP 栈。
|
||||
|
||||
| 栈 | 描述 | 状态 |
|
||||
|------------------|--------------------------------------------------------------------------|-------|
|
||||
| gVisor (default) | 基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 |
|
||||
| system | 兼容性较差,有时性能更好。 | 推荐 |
|
||||
| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 LWIP 栈,请参阅 [安装](/zh/#_2)。
|
||||
|
||||
#### include_uid
|
||||
|
||||
!!! error ""
|
||||
|
||||
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
||||
|
||||
限制被路由的的用户。默认不限制。
|
||||
|
||||
#### include_uid_range
|
||||
|
||||
限制被路由的的用户范围。
|
||||
|
||||
#### exclude_uid
|
||||
|
||||
排除路由的的用户。
|
||||
|
||||
#### exclude_uid_range
|
||||
|
||||
排除路由的的用户范围。
|
||||
|
||||
#### include_android_user
|
||||
|
||||
!!! error ""
|
||||
|
||||
Android 用户和应用规则仅在 Android 下被支持,并且需要 `auto_route`。
|
||||
|
||||
限制被路由的 Android 用户。
|
||||
|
||||
| 常用用户 | ID |
|
||||
|--|-----|
|
||||
| 您 | 0 |
|
||||
| 工作资料 | 10 |
|
||||
|
||||
#### include_package
|
||||
|
||||
限制被路由的 Android 应用包名。
|
||||
|
||||
#### exclude_package
|
||||
|
||||
排除路由的 Android 应用包名。
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
@@ -2,73 +2,33 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
"type": "vmess",
|
||||
"tag": "vmess-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"type": "vmess",
|
||||
"tag": "vmess-in",
|
||||
|
||||
"listen": "::",
|
||||
"listen_port": 2080,
|
||||
"tcp_fast_open": false,
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"alterId": 0
|
||||
}
|
||||
],
|
||||
"tls": {}
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"alterId": 0
|
||||
}
|
||||
]
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
#### listen
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable tcp fast open for listener.
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
|
||||
See [Sniff](/configuration/route/sniff/) for details.
|
||||
|
||||
#### sniff_override_destination
|
||||
|
||||
Override the connection destination address with the sniffed domain.
|
||||
|
||||
If the domain name is invalid (like tor), this will not work.
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before routing.
|
||||
|
||||
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||
|
||||
### VMess Fields
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
==Required==
|
||||
|
||||
VMess users.
|
||||
|
||||
| Alter ID | Description |
|
||||
@@ -82,4 +42,8 @@ VMess users.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
49
docs/configuration/inbound/vmess.zh.md
Normal file
49
docs/configuration/inbound/vmess.zh.md
Normal file
@@ -0,0 +1,49 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vmess",
|
||||
"tag": "vmess-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"alterId": 0
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
VMess 用户。
|
||||
|
||||
| Alter ID | 描述 |
|
||||
|----------|-------|
|
||||
| 0 | 禁用旧协议 |
|
||||
| > 0 | 启用旧协议 |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
提供旧协议支持(VMess MD5 身份验证)仅出于兼容性目的,不建议使用 alterId > 1。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
@@ -8,8 +8,8 @@ sing-box uses JSON for configuration files.
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"inbounds": {},
|
||||
"outbounds": {},
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"experimental": {}
|
||||
}
|
||||
|
||||
39
docs/configuration/index.zh.md
Normal file
39
docs/configuration/index.zh.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 引言
|
||||
|
||||
sing-box 使用 JSON 作为配置文件格式。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"experimental": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
| Key | Format |
|
||||
|----------------|-----------------------|
|
||||
| `log` | [日志](./log) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `inbounds` | [入站](./inbound) |
|
||||
| `outbounds` | [出站](./outbound) |
|
||||
| `route` | [路由](./route) |
|
||||
| `experimental` | [实验性](./experimental) |
|
||||
|
||||
### 检查
|
||||
|
||||
```bash
|
||||
$ sing-box check
|
||||
```
|
||||
|
||||
### 格式化
|
||||
|
||||
```bash
|
||||
$ sing-box format -w
|
||||
```
|
||||
33
docs/configuration/log/index.zh.md
Normal file
33
docs/configuration/log/index.zh.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 日志
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"disabled": false,
|
||||
"level": "info",
|
||||
"output": "box.log",
|
||||
"timestamp": true
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### disabled
|
||||
|
||||
禁用日志,启动后不输出日志。
|
||||
|
||||
#### level
|
||||
|
||||
日志等级,可选值:`trace` `debug` `info` `warn` `error` `fatal` `panic`。
|
||||
|
||||
#### output
|
||||
|
||||
输出文件路径,启动后将不输出到控制台。
|
||||
|
||||
#### timestamp
|
||||
|
||||
添加时间到每行。
|
||||
@@ -4,15 +4,11 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
]
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
### 字段
|
||||
|
||||
No fields.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user