Compare commits

..

174 Commits

Author SHA1 Message Date
世界
d2add33723 Bump version 2022-09-15 13:12:18 +08:00
世界
ab0daf31c1 Fix clash api proxy type 2022-09-15 13:11:52 +08:00
世界
3d94b948dd Fix port rule match logic 2022-09-15 13:11:20 +08:00
世界
1659ae5d79 Fix close grpc conn 2022-09-15 13:10:18 +08:00
世界
7279855b08 Bump version 2022-09-13 11:25:38 +08:00
世界
925fbca363 Fix concurrent write 2022-09-13 10:36:37 +08:00
世界
a5163e3e3c Fix hysteria inbound 2022-09-13 10:32:14 +08:00
世界
62859e0c6b Fix socks4 client 2022-09-13 10:32:12 +08:00
世界
a37cab48d2 Bump version 2022-09-10 23:13:58 +08:00
世界
c586c8f361 Fix socks4 request 2022-09-10 22:53:06 +08:00
世界
e68fa3e12d Fix processing empty dns result 2022-09-10 22:52:54 +08:00
世界
7f5b9e0e3b Run build on main branch 2022-09-10 22:52:54 +08:00
世界
f7bed32c6f Bump version 2022-09-09 14:43:42 +08:00
世界
ef7f2d82c0 Fix match 4in6 address in ip_cidr 2022-09-09 14:07:02 +08:00
世界
7aa97a332e Fix documentation 2022-09-09 13:54:02 +08:00
世界
7c30dde96b Minor fixes 2022-09-08 18:33:59 +08:00
GyDi
9cef2a0a8f Fix clashapi log level format error 2022-09-08 18:04:06 +08:00
世界
f376683fc3 Update documentation 2022-09-07 23:10:36 +08:00
世界
4b61d6e875 Fix hysteria stream error 2022-09-07 19:16:20 +08:00
世界
7d83e350fd Refine test 2022-09-07 19:16:20 +08:00
世界
500ba69548 Fix processing vmess termination signal 2022-09-07 19:16:20 +08:00
世界
9a422549b1 Fix json format error message 2022-09-07 13:23:26 +08:00
世界
3b48fa455e Fix naive inbound temporary 2022-09-07 12:30:54 +08:00
zakuwaki
ef013e0639 Suppress accept proxyproto failed #65 2022-09-06 23:16:31 +08:00
世界
8f8437a88d Fix wireguard reconnect 2022-09-06 00:11:43 +08:00
世界
1b091c9b07 Update documentation 2022-09-04 13:15:10 +08:00
世界
4801b6f057 Fix DNS routing 2022-09-04 12:49:38 +08:00
世界
9078bc2de5 Fix write trojan udp 2022-09-03 16:58:55 +08:00
世界
b69464dfe9 Update documentation for dial fields 2022-09-03 13:02:41 +08:00
世界
62fa48293a Merge dialer options 2022-09-03 12:55:10 +08:00
世界
b206d0889b Fix dial parallel in direct outbound 2022-09-03 12:01:48 +08:00
世界
ee691d81bf Fix write zero 2022-09-03 09:25:30 +08:00
void aire()
56876a67cc Fix documentation typo (#60) 2022-09-02 19:04:03 +08:00
世界
4a0df713aa Add ws compatibility test 2022-09-01 20:32:47 +08:00
世界
ef801cbfbe Fix server install script 2022-09-01 20:32:47 +08:00
世界
9378fc88d2 Add with_wireguard to default server tag 2022-09-01 20:16:20 +08:00
世界
f46bfcc3d8 Move unstable branch to dev-next 2022-08-31 23:45:42 +08:00
0x7d274284
ccdb238843 Fix documentation typo (#57) 2022-08-31 23:42:36 +08:00
世界
f1f61b4e2b Fix install documentation 2022-08-31 23:37:30 +08:00
世界
a44cb745d9 Fix write log timestamp 2022-08-31 23:35:43 +08:00
世界
f5f5cb023c Update documentation 2022-08-31 14:34:32 +08:00
世界
5813e0ce7a Add shadowtls (#49)
* Add shadowtls outbound

* Add shadowtls inbound

* Add shadowtls example

* Add shadowtls documentation
2022-08-31 14:21:53 +08:00
dyhkwong
5a9c2b1e80 darwin pf support (#52) 2022-08-31 14:21:37 +08:00
世界
bda34fdb3b Refactor outbound documentation 2022-08-31 13:42:30 +08:00
世界
426b677eb8 Fix process_name rule item 2022-08-31 12:51:38 +08:00
世界
67c7e9fd86 Refactor inbound documetation 2022-08-31 12:50:26 +08:00
世界
d8028a8632 Fix smux session status 2022-08-31 10:00:15 +08:00
dyhkwong
374743d022 Add process_path rule item (#51)
* process matching supports full path
* Remove strings.ToLower
2022-08-30 10:44:40 +08:00
世界
cd98ea5008 Fix socksaddr type condition 2022-08-29 19:58:58 +08:00
世界
dbda0ed98a Add chained inbound support 2022-08-29 19:50:28 +08:00
世界
f5e0ead01c Fix inject conn 2022-08-29 19:02:41 +08:00
0x7d274284
44818701bc Fix issue template (#48)
The correct command to get the version is `sing-box version`
2022-08-29 16:52:15 +08:00
世界
e0f7387dff Fix search android package in non-owner users 2022-08-29 12:02:29 +08:00
世界
d440a01792 Add grpc compatibility test 2022-08-29 10:15:25 +08:00
世界
665c84ee42 Fix log item on document menu 2022-08-28 12:47:23 +08:00
Hellojack
e0de96eb4c Minor fixes (#45)
* Cleanup code
* Fix documentation typo
2022-08-28 12:40:44 +08:00
世界
c6ef276811 Update dependencies 2022-08-28 12:21:22 +08:00
世界
1701aaf78c Add docker image 2022-08-28 00:23:41 +08:00
世界
122daa4bfb Simplify server installation 2022-08-28 00:23:41 +08:00
世界
561a9e5275 Update documentation 2022-08-28 00:23:41 +08:00
Hellojack
de2453fce9 Add gun-lite gRPC implementation (#44) 2022-08-27 21:05:15 +08:00
世界
d59d40c118 Fix sniff override destination 2022-08-27 14:37:14 +08:00
rand0mgh0st
3469df001f Fix documentation for socks inbound (#42) 2022-08-27 13:16:04 +08:00
世界
0d8cfa3031 Add vmess packetaddr option 2022-08-27 11:28:01 +08:00
世界
0289586880 Add documentation for strict_route 2022-08-27 09:31:17 +08:00
rand0mgh0st
e46427c7fc docs-zh-CN: use English for License section (#40) 2022-08-26 23:21:32 +08:00
世界
3ea59d9a8e Move documentation branch to main 2022-08-26 21:53:46 +08:00
世界
e85dfc6adf Add strict_route option 2022-08-26 21:53:08 +08:00
世界
d0703b78fa Fix dns hijack on android
iproute2 on android does not support port rules
2022-08-26 21:05:45 +08:00
世界
432e6adf3e Fix TLS documentation 2022-08-26 18:36:56 +08:00
世界
a057754035 Revert linux process searcher 2022-08-26 17:36:06 +08:00
世界
0348ace253 Initial release 2022-08-26 16:40:37 +08:00
世界
c5e38203eb Fix read DNS message 2022-08-26 13:35:27 +08:00
世界
9ac31d0233 Fix ipv6 route on linux 2022-08-26 12:30:31 +08:00
世界
9d8d1cd69d Update documentation 2022-08-26 11:10:02 +08:00
世界
07a0381f8b Cleanup vmessws 2022-08-26 10:22:29 +08:00
世界
f841459004 Cleanup vmesshttp 2022-08-26 08:41:45 +08:00
世界
78a26fc139 Update documentation 2022-08-25 22:49:23 +08:00
世界
9f6628445e Improve ip_cidr rule 2022-08-25 22:23:26 +08:00
世界
fa017b5977 Add contributing documentation 2022-08-25 21:08:29 +08:00
世界
58f4a970f2 Fix route connections 2022-08-25 20:48:59 +08:00
世界
021aa8faed Fix ipv6 route on linux 2022-08-25 18:57:36 +08:00
世界
83f6e037d6 Fix http proxy with compressed response 2022-08-25 18:40:13 +08:00
世界
baf153434d Fix issue template 2022-08-25 18:40:13 +08:00
世界
d481bd7993 Fix bind_address 2022-08-25 14:50:10 +08:00
Steven Tang
e859c0a6ef Fix typo in features.md (#32) 2022-08-25 13:42:22 +08:00
zakuwaki
59a39e66b1 Add trojan fallback for ALPN #31 2022-08-25 13:37:32 +08:00
世界
fd5ac69a35 Let vmess use zero instead of auto if TLS enabled 2022-08-25 11:51:17 +08:00
世界
a940703ae1 Suppress expected error 2022-08-25 11:02:27 +08:00
世界
350729cde8 Remove TLS requirement on gRPC server 2022-08-25 10:52:16 +08:00
世界
2e14cd6d66 Close websocket conn gracefully 2022-08-25 10:46:14 +08:00
世界
f703524f04 Add stale workflow 2022-08-25 10:24:11 +08:00
世界
aa4435c775 Update documentation 2022-08-25 10:04:51 +08:00
Reece
31a2e368cc Fix zh-CN document symbol and format (#29) 2022-08-25 09:45:22 +08:00
世界
97e284e65e Initial zh-CN document translation: outbound 2022-08-24 21:02:28 +08:00
世界
a6baab92f3 Fix early close on windows and catch any 2022-08-24 19:03:15 +08:00
世界
7c76e0c3ee Initial zh-CN document translation: inbound 2022-08-24 18:43:39 +08:00
世界
591a4fcf8e Initial zh-CN document translation: shared 2022-08-24 17:39:37 +08:00
世界
71dac85600 Add ACME EAB support 2022-08-24 17:06:28 +08:00
世界
ad90ddd327 Initial zh-CN document translation: route 2022-08-24 16:56:29 +08:00
世界
03f457f3d0 Initial zh-CN document translation: DNS 2022-08-24 16:37:06 +08:00
Hellojack
a878256367 Fix TLS insecure (#27) 2022-08-24 16:11:41 +08:00
世界
553f78ed55 Fix close non-duplex connections 2022-08-24 14:32:18 +08:00
世界
1bc7d2237e Initial zh-CN document translation: examples 2022-08-24 13:14:12 +08:00
世界
132222013b Initial zh-CN document translation: FAQ 2022-08-24 13:04:47 +08:00
世界
2008fb552a Initial zh-CN document translation 2022-08-24 12:45:51 +08:00
世界
236c034c62 Fix unix search path 2022-08-24 12:27:36 +08:00
世界
f87baf08d3 Fix naive padding 2022-08-24 10:21:56 +08:00
世界
22aa0c2f40 Update documentation 2022-08-24 00:39:25 +08:00
世界
88469d4aaa Check configuration before reload 2022-08-23 23:44:44 +08:00
世界
1413c5022a Add proxy protocol support 2022-08-23 21:07:35 +08:00
世界
aa8cdaee22 Handle SIGHUP signal 2022-08-23 19:56:28 +08:00
世界
9f6ff54a76 Parse X-Forward-For in HTTP requests 2022-08-23 19:53:04 +08:00
世界
e750c747c6 Fix test naive inbound with nginx 2022-08-23 14:41:31 +08:00
世界
9edfe7d9d3 Accept HTTP1 in naive inbound 2022-08-23 13:25:03 +08:00
世界
c9b7acd22c Add v2ray transport to trojan 2022-08-23 13:24:52 +08:00
世界
2ba2f0298c Free memory after start 2022-08-22 23:17:08 +08:00
世界
a24a2b475a Allow http1 in v2ray HTTP transport 2022-08-22 23:02:25 +08:00
世界
4005452772 Add v2ray HTTP transport 2022-08-22 22:20:19 +08:00
世界
d4b7e221f0 Add v2ray QUIC transport 2022-08-22 22:20:19 +08:00
世界
77c98fd042 Add v2ray WebSocket transport 2022-08-22 22:20:18 +08:00
世界
082872b2f3 Prepare v2ray client/server transport 2022-08-22 18:57:05 +08:00
世界
6253e2e24c Fix clash server early close 2022-08-22 16:33:33 +08:00
世界
4216afe62f Minor fixes 2022-08-22 16:14:53 +08:00
世界
8fec78a5cd Apply bind address to udp connect 2022-08-22 14:35:05 +08:00
世界
7ba0a14e97 Add bind address to outbound options 2022-08-22 14:28:23 +08:00
世界
3a442347a5 Update documentation 2022-08-22 14:19:32 +08:00
世界
c4f4fd97d6 Fix tests 2022-08-22 12:02:16 +08:00
世界
ac0ead1473 Add strategy setting for each dns server 2022-08-22 12:01:50 +08:00
世界
83cea9475d Fix vectorised writer 2022-08-21 22:35:58 +08:00
世界
dc6bb7ab1b Add ssh outbound 2022-08-21 22:30:48 +08:00
世界
c71f6ba377 Add FAQ page 2022-08-21 22:26:08 +08:00
世界
b1b1ab5350 Update release config 2022-08-21 13:03:19 +08:00
世界
7613b8dbfe Fix gvisor udp write back 2022-08-21 11:40:04 +08:00
世界
e4cece6095 Add tor outbound 2022-08-21 01:06:34 +08:00
世界
bcefe8716f Fix typo in documentation 2022-08-20 21:16:14 +08:00
世界
746b5d8be0 Add trojan connection fallback 2022-08-20 21:08:53 +08:00
世界
f13ecbd9bb Wait a second before check route update 2022-08-20 13:42:28 +08:00
世界
e839beb73b Skip bind connection with private destination to interface 2022-08-20 13:31:15 +08:00
世界
b797cdf91e Fix write socks5 username password auth request 2022-08-20 13:26:49 +08:00
世界
84e4677a94 Improve process searcher 2022-08-20 12:11:27 +08:00
世界
0377a11719 Fix route on android 2022-08-20 10:27:13 +08:00
世界
d0fa79044a Start outbounds before router 2022-08-20 09:13:00 +08:00
世界
f381f8d35a Fix read packages in android 13 2022-08-20 03:05:50 +08:00
世界
92e1e5b893 Attempt to unwrap ip-in-fqdn socksaddr 2022-08-20 00:01:08 +08:00
世界
8e8b4dba22 Update documentation 2022-08-19 22:30:12 +08:00
世界
767cd55817 Fix acme issuer 2022-08-19 18:42:12 +08:00
世界
eb0ef439d6 Add with_acme to server scripts 2022-08-19 17:48:56 +08:00
世界
0bf78c0a8a Update gVisor to 20220815.0 2022-08-19 17:47:54 +08:00
世界
12d7e19f32 Allow read config from stdin 2022-08-19 15:43:13 +08:00
世界
d1c3dd0ee1 Add hysteria and acme TLS certificate issuer (#18)
* Add hysteria client/server
* Add acme TLS certificate issuer
2022-08-19 15:42:57 +08:00
世界
3dfa99efe1 Add back dns concurrent write lock 2022-08-19 10:51:26 +08:00
世界
d7bd221a47 Fix darwin tun 2022-08-19 08:35:08 +08:00
世界
1b7a3b4a74 Fix log to file 2022-08-19 08:26:26 +08:00
世界
c8424ed8fd Fix format 2022-08-19 08:26:26 +08:00
世界
150df1ae8e Add write lock to shadowsocks aead writer 2022-08-19 08:26:26 +08:00
世界
5ca9d77176 Fix close shadowsocks server conn 2022-08-18 23:16:05 +08:00
世界
aa89fcc29d Fix find process with lwip stack 2022-08-18 10:10:30 +08:00
Tianling Shen
7ead0de26b Fix geosite path (#17)
`geoIPOptions` -> `geositeOptions`

Signed-off-by: Tianling Shen <i@cnsztl.eu.org>
2022-08-18 10:00:56 +08:00
世界
f22c2690ec Fix lint 2022-08-17 20:15:35 +08:00
世界
738bb0eabc Improve async dns transports 2022-08-17 20:10:59 +08:00
世界
002a519a17 Update documentation 2022-08-17 15:19:10 +08:00
世界
f51128f772 Add ip_version rule item 2022-08-16 23:47:14 +08:00
世界
d6a0aa7ccf Add wireguard outbound and test 2022-08-16 23:39:11 +08:00
世界
ca94a2ddcb Improve tproxy udp write back 2022-08-16 18:37:37 +08:00
世界
835ae1217b Update exec/control usage 2022-08-16 18:19:48 +08:00
世界
c165969399 Fix include_android_user option 2022-08-16 12:16:59 +08:00
世界
88c69a06dc Fix copy stream 2022-08-15 16:53:12 +08:00
世界
cd5e7055d2 Add android package rules support in tun routing 2022-08-15 11:44:59 +08:00
世界
3157593b6b Add uid and android user rules support in tun routing 2022-08-15 11:41:00 +08:00
世界
c8399a297e Improve cmd 2022-08-13 18:37:51 +08:00
Hellojack
529cfe2d9a Fix documentation typo (#13) 2022-08-13 11:01:22 +08:00
世界
50869c6cd2 Fix dns concurrent write 2022-08-13 11:00:15 +08:00
世界
44fcfab9aa Improve build 2022-08-12 22:58:28 +08:00
309 changed files with 15890 additions and 3010 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -3,14 +3,18 @@ name: Debug build
on:
push:
branches:
- main
- dev
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- main
- dev
- dev-next
jobs:
build:
@@ -47,6 +51,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 +165,7 @@ jobs:
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
CGO_ENABLED: 0
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@v2

43
.github/workflows/docker.yml vendored Normal file
View 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

View File

@@ -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
View 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
View File

@@ -3,4 +3,5 @@
/*.json
/*.db
/site/
/bin/
/bin/
/dist/

View File

@@ -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.19'

85
.goreleaser.yaml Normal file
View 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
View 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"]

View File

@@ -1,46 +1,74 @@
NAME=sing-box
COMMIT=$(shell git rev-parse --short HEAD)
PARAMS=-trimpath -tags '$(TAGS)' -ldflags \
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
MAIN = ./cmd/sing-box
.PHONY: test
.PHONY: test release
build:
go build $(PARAMS) $(MAIN)
action_version: build
echo "::set-output name=VERSION::`./sing-box version -n`"
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
fmt:
gofumpt -l -w .
gofmt -s -w .
gci write -s "standard,prefix(github.com/sagernet/),default" .
lint_install:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
lint:
GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
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 . && \
@go test -v . && \
pushd test && \
go test -v . && \
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:

View File

@@ -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{}

View File

@@ -37,6 +37,7 @@ type Router interface {
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
SetTrafficController(controller TrafficController)
}

View File

@@ -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
View 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
View File

@@ -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 {

View 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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -21,6 +21,7 @@ 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) {
@@ -37,7 +38,7 @@ func printVersion(cmd *cobra.Command, args []string) {
version += runtime.Version()
version += ", "
version += runtime.GOOS
version += ", "
version += "/"
version += runtime.GOARCH
version += ", "
version += "CGO "

View File

@@ -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() {

View 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
View 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()
}
}
}

View File

@@ -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,35 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
}
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener}
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)
}

View File

@@ -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))

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View 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)
}
}

View File

@@ -0,0 +1,40 @@
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
}
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 {
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)
}
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr),
Destination: M.SocksaddrFromNet(header.DestinationAddr),
}}, nil
}

View 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
}

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
//go:build !linux
//go:build !linux && !darwin
package redir

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -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"
@@ -19,7 +21,7 @@ type systemProxy struct {
}
func (p *systemProxy) update() error {
newInterfaceName := p.monitor.DefaultInterfaceName()
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
}

View File

@@ -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")
}

View File

@@ -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))

View File

@@ -1,11 +0,0 @@
package constant
type DomainStrategy = uint8
const (
DomainStrategyAsIS DomainStrategy = iota
DomainStrategyPreferIPv4
DomainStrategyPreferIPv6
DomainStrategyUseIPv4
DomainStrategyUseIPv6
)

5
constant/err.go Normal file
View File

@@ -0,0 +1,5 @@
package constant
import E "github.com/sagernet/sing/common/exceptions"
var ErrTLSRequired = E.New("TLS required")

View File

@@ -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

View File

@@ -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")

View File

@@ -14,6 +14,11 @@ const (
TypeVMess = "vmess"
TypeTrojan = "trojan"
TypeNaive = "naive"
TypeWireGuard = "wireguard"
TypeHysteria = "hysteria"
TypeTor = "tor"
TypeSSH = "ssh"
TypeShadowTLS = "shadowtls"
)
const (

View File

@@ -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
View File

@@ -0,0 +1,8 @@
package constant
const (
V2RayTransportTypeHTTP = "http"
V2RayTransportTypeWebsocket = "ws"
V2RayTransportTypeQUIC = "quic"
V2RayTransportTypeGRPC = "grpc"
)

View File

@@ -1,6 +1,6 @@
package constant
var (
Version = "20220812"
Version = "1.0.4"
Commit = ""
)

View File

@@ -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 |

View File

@@ -1,19 +1,152 @@
#### 2022/08/12
#### 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

View File

@@ -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.

View 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 缓存过期。

View File

@@ -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.

View 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
在此查询中禁用缓存。

View File

@@ -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.

View 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 服务器的出站的标签。
如果为空,将使用默认出站。

View File

@@ -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

View 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,请始终设置一个密钥。

View File

@@ -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

View 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
覆盖连接目标端口。

View File

@@ -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.

View 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。
启动时自动设置系统代理,停止时自动清理。

View 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).

View 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)。

View File

@@ -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

View 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
入站的标签。

View File

@@ -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.

View 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。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -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).

View 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)。

View File

@@ -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.

View File

@@ -0,0 +1,17 @@
!!! error ""
仅支持 Linux 和 macOS。
### 结构
```json
{
"type": "redirect",
"tag": "redirect-in",
... // 监听字段
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@@ -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.

View 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 | 任意字符串 |

View 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).

View 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/)。

View File

@@ -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.

View 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 用户
如果为空则不需要验证。

View File

@@ -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.

View File

@@ -0,0 +1,28 @@
!!! error ""
仅支持 Linux。
### 结构
```json
{
"type": "tproxy",
"tag": "tproxy-in",
... // 监听字段
"network": "udp"
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### network
监听的网络协议,`tcp` `udp` 之一。
默认所有。

View File

@@ -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).

View 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)。

View File

@@ -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": 1500,
"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,6 +80,16 @@ Set the default route to the Tun.
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
#### 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
Enable endpoint-independent NAT.
@@ -81,26 +111,49 @@ TCP/IP stack.
!!! 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.

View File

@@ -0,0 +1,158 @@
!!! 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": 1500,
"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`
#### strict_route
在 Linux 中启用 `auto_route` 时执行严格的路由规则。
* 让不支持的网络无法到达
* 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
#### endpoint_independent_nat
启用独立于端点的 NAT。
性能可能会略有下降,所以不建议在不需要的时候开启。
#### udp_timeout
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
#### stack
TCP/IP 栈。
| 栈 | 上游 | 状态 |
|------------------|-----------------------------------------------------------------------|-------|
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | 推荐 |
| 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/)。

View File

@@ -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).

View 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)。

View File

@@ -8,8 +8,8 @@ sing-box uses JSON for configuration files.
{
"log": {},
"dns": {},
"inbounds": {},
"outbounds": {},
"inbounds": [],
"outbounds": [],
"route": {},
"experimental": {}
}

View 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
```

View 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
添加时间到每行。

View File

@@ -4,15 +4,11 @@
```json
{
"outbounds": [
{
"type": "block",
"tag": "block"
}
]
"type": "block",
"tag": "block"
}
```
### Fields
### 字段
No fields.

View File

@@ -0,0 +1,14 @@
`block` 出站关闭所有传入请求。
### 结构
```json
{
"type": "block",
"tag": "block"
}
```
### 字段
无字段。

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