Compare commits

..

194 Commits

Author SHA1 Message Date
世界
ec4a0c8497 Update documentation 2023-02-18 15:02:27 +08:00
世界
21cb227bc2 Add ShadowTLS protocol v3 2023-02-18 14:55:47 +08:00
世界
1610bdc5dd Update workflow 2023-02-18 14:28:21 +08:00
世界
3296a2f7b2 Update dependencies 2023-02-18 14:28:21 +08:00
世界
2bd91baad0 Add fallback support for v2ray transport 2023-02-18 14:28:21 +08:00
世界
a624cd9b49 Disable vmess header protection if transport enabled 2023-02-13 05:15:26 +08:00
Tim Xylon
02afba132f Replace deprecated 'set-output' 2023-02-09 22:07:00 +08:00
世界
99890a1af0 Fix socks connect response 2023-02-09 21:25:00 +08:00
世界
437f1f819c Fix lint 2023-02-09 21:01:48 +08:00
世界
92a79e6158 Remove cancel-workflow-action 2023-02-09 18:04:49 +08:00
renovate[bot]
c9efd0a74f [dependencies] Update golang Docker tag to v1.20
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 18:04:16 +08:00
renovate[bot]
9da349748a [dependencies] Update github-actions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 17:42:06 +08:00
世界
2423cbbbfe Add renovate config 2023-02-09 17:39:15 +08:00
Gavin Luo
4833f6d5db Fix systemd service caps for process sniffing 2023-02-09 13:32:31 +08:00
世界
9db3cb5cb7 Update scripts 2023-02-09 13:27:05 +08:00
shadow750d6
c14b353a29 Fix parse hysteria UDP message 2023-02-09 13:20:16 +08:00
世界
19d08b55c8 Update documentation 2023-02-08 17:35:50 +08:00
世界
39514b3ca0 Add v2ray user stats api 2023-02-08 16:50:15 +08:00
世界
7ea9d48987 Add DHCP DNS server support 2023-02-08 16:43:29 +08:00
lyc8503
df3a982141 Add SSH outbound host key validation 2023-02-08 16:20:48 +08:00
世界
687b4509df Add query_type DNS rule item 2023-02-08 16:18:40 +08:00
世界
41ec2e7944 Add support for clash DNS query API 2023-02-08 14:13:33 +08:00
世界
1bd3a9144d Upgrade tfo-go 2023-02-08 14:13:33 +08:00
世界
6e852cc99b Update dependencies 2023-02-08 14:13:15 +08:00
世界
8320dd0b51 Improve vmess request 2023-02-07 14:53:08 +08:00
世界
960d04d172 Fix match geoip 2023-02-07 12:21:00 +08:00
世界
86ea035bdd Fix ipv6 redir port 2023-02-05 14:48:02 +08:00
世界
9b6449dcf4 Fix find NDK on macOS 2023-02-02 16:30:50 +08:00
世界
4e22ac1a35 Update documentation 2023-02-02 16:11:29 +08:00
世界
8a779f6e94 Update dependencies 2023-02-02 15:38:48 +08:00
世界
d461768ffb Fix build with go1.20 2023-02-02 15:25:34 +08:00
Dmitry R
5d41e328d4 ignore domain case in route rules 2023-02-02 15:25:34 +08:00
世界
fe492904e9 Fix auth_user route for naive inbound 2023-01-19 10:47:22 +08:00
世界
168253b851 Fix inbound default DF 2023-01-19 10:36:25 +08:00
Hellojack
05620a369e Fix gRPC lite header
Manually set the first byte to 0x00 (No Compression) since we can not ensure that the buffer is not polluted before.
2023-01-16 16:23:06 +08:00
世界
8e0fe55363 Fix wireguard events 2023-01-15 19:48:50 +08:00
世界
59e521c1db Fix convert netaddr 2023-01-14 20:04:15 +08:00
世界
f32c149738 Bump version 2023-01-14 16:01:07 +08:00
世界
23a35b3c06 Fix create UDP DNS transport from plain IPv6 address 2023-01-13 11:51:20 +08:00
世界
044f9c5d4f Fix write to h2 conn after closed 2023-01-08 15:43:12 +08:00
世界
54f9625bdc Fix DNS log 2023-01-03 17:04:10 +08:00
世界
ff0693be32 Bump version 2023-01-03 10:53:38 +08:00
世界
53d9ad93e3 Improve DNS log 2023-01-03 10:36:18 +08:00
世界
f5c5570bec Skip set windows system proxy bypass list 2022-12-26 12:33:08 +08:00
世界
53f19a6ead Fix override packet conn 2022-12-21 21:58:03 +08:00
世界
cfaf15f429 Fix DNS response TTL 2022-12-19 13:10:57 +08:00
世界
9e67f3b4a5 Fix user from stream packet conn 2022-12-18 16:08:49 +08:00
isnowly
4d2185a2d4 Fix http proxy auth 2022-12-18 16:08:49 +08:00
世界
33f22263ca Update dependencies 2022-12-18 15:40:19 +08:00
世界
d09aa07d21 Fix android i686 compiler 2022-12-11 15:01:54 +08:00
世界
8afb8ca7eb Update documentation 2022-12-11 14:40:03 +08:00
世界
80ed5bf8fb Fix android package 2022-12-11 14:38:01 +08:00
世界
81e7b0b320 Fix linux package 2022-12-11 11:51:25 +08:00
世界
a828c3b5da Fix acme config 2022-12-06 13:36:42 +08:00
世界
c95e4a13a1 Fix vmess packet conn 2022-12-06 13:02:28 +08:00
世界
726a7e19eb Suppress quic-go set DF error 2022-12-06 12:51:52 +08:00
世界
8953ddc6e0 Update workflow 2022-12-03 17:03:04 +08:00
世界
7ebbd58b00 Update documentation 2022-12-03 14:38:52 +08:00
世界
d0095fd0f4 Fix close clash cache 2022-12-03 13:29:37 +08:00
世界
66d8d563eb Add WorkingDirectory for systemd service 2022-11-28 18:30:50 +08:00
世界
4bf96c7eb5 Fix quic stub 2022-11-28 16:06:54 +08:00
世界
f687c25fa9 Update documentation 2022-11-28 13:11:07 +08:00
世界
a92412ecac Fix router 2022-11-28 13:11:07 +08:00
世界
8dcafa5b33 Add trojan-go multiplex support for trojan inbound 2022-11-28 12:51:23 +08:00
世界
7a02cb83a7 Revert "Fix listen packet on address"
This reverts commit d1fe17a4db.
2022-11-28 12:51:23 +08:00
世界
51ce672076 Fix crash when input bad method in shadowsocks multi-user inbound 2022-11-28 12:51:23 +08:00
世界
7734afc40c Update dependencies 2022-11-28 12:51:23 +08:00
世界
ee3cd49aa5 Fix tls config for h2 server 2022-11-26 14:55:51 +08:00
世界
bf20ff84b5 Fix lint 2022-11-26 11:21:24 +08:00
世界
c58302554c Fix documentation 2022-11-26 11:06:57 +08:00
世界
05ed88aba8 Update documentation 2022-11-25 22:59:30 +08:00
世界
9f5cc0442b Fix dockerfile 2022-11-25 22:59:30 +08:00
世界
2641a43ad8 Remove test on pull request 2022-11-25 21:12:45 +08:00
世界
4a6ab5e9fd Fix cancel on start 2022-11-25 21:12:45 +08:00
世界
d1fe17a4db Fix listen packet on address 2022-11-25 21:12:45 +08:00
世界
7c910165ef Cleanup code 2022-11-24 12:37:29 +08:00
世界
8c1fddcf8d Remove connect packet conn 2022-11-24 12:01:25 +08:00
Hellojack
01b4769852 Cleanup gun conn code 2022-11-23 14:56:31 +08:00
世界
a401828ed5 Fix shadowtls server detection 2022-11-22 22:15:38 +08:00
世界
ffd54eef6c Update documentation 2022-11-21 21:20:44 +08:00
世界
c16e4316d6 Fix shadowtls server 2022-11-21 21:20:44 +08:00
世界
8b7fe20b7f Include uTLS in release 2022-11-21 15:25:49 +08:00
世界
696c1065b6 Update stable documentation 2022-11-21 14:57:22 +08:00
世界
5d690f4147 Update documentation 2022-11-21 13:18:04 +08:00
世界
f906641a82 Add uTLS to makefile default tags 2022-11-21 13:18:04 +08:00
世界
89913dfa8c Improve shadowtls server 2022-11-21 13:18:04 +08:00
世界
468778f67f Update dependencies 2022-11-21 13:18:04 +08:00
世界
22a22aebe2 Fix default dns transport strategy 2022-11-21 13:18:04 +08:00
世界
a2d2ec9b45 Update documentation 2022-11-15 17:36:42 +08:00
世界
2695b3516e Update issue template 2022-11-13 11:45:24 +08:00
世界
3a9ef8fac0 Remove unused 2022-11-13 11:30:48 +08:00
世界
ebad363201 Fix create TLS config 2022-11-13 11:24:37 +08:00
世界
11076d52cd Fix dns buffer & quic retry 2022-11-13 11:16:10 +08:00
世界
5eb132063e Fix connect packet connection for mux client 2022-11-12 03:53:42 +08:00
世界
13ab5d3348 Remove follow in update script 2022-11-11 22:32:24 +08:00
世界
ce1ddc400f Support x/h2 v0.2.0 deadline 2022-11-11 22:08:20 +08:00
arm64v8a
2c9d25e853 Fix websocket alpn 2022-11-11 20:01:49 +08:00
世界
3d76777760 Fix tor geoip 2022-11-10 22:42:05 +08:00
世界
24f4dfea04 Fix hysteria test 2022-11-10 21:10:18 +08:00
世界
2fc1a0a9dd Update documentation 2022-11-10 16:33:10 +08:00
世界
617aba84e4 Add multi user support for hysteria inbound 2022-11-09 21:00:08 +08:00
世界
5510c474c7 Fix h2c transport 2022-11-09 12:15:14 +08:00
世界
eb2e8a0b40 Add custom tls client support for std grpc 2022-11-09 11:46:29 +08:00
世界
972491c19d Fix default local DNS server behavior 2022-11-09 10:35:16 +08:00
世界
7358ca4a52 Fix vmess request buffer 2022-11-09 10:16:22 +08:00
世界
61c274045a Update install go script 2022-11-08 23:19:53 +08:00
世界
f205140b04 Fix smux keep alive 2022-11-08 16:45:38 +08:00
世界
1db8e03c86 Fix format 2022-11-08 14:54:19 +08:00
世界
2ecf86c2bc Update patched quic-go 2022-11-08 13:54:01 +08:00
世界
999a847e86 Add custom wireguard worker size option 2022-11-08 13:48:14 +08:00
世界
1f63ce5dee Fix reset outbound 2022-11-06 10:36:19 +08:00
世界
0ad1bbea11 Fix wireguard close 2022-11-06 10:20:23 +08:00
世界
b2cd78d279 Move WFP manipulation to strict route 2022-11-06 10:16:07 +08:00
世界
d5bb58a0b4 Update documentation 2022-11-06 10:16:07 +08:00
世界
7f84936050 Split bind_address 2022-11-06 10:16:07 +08:00
Dreamacro
6adfea0a72 Fix macOS Ventura process name match 2022-11-06 10:16:07 +08:00
Hellojack
10f213bf3d Adjust uTLS wrapper 2022-11-06 10:16:07 +08:00
世界
6e8c4f6576 Update documentation 2022-10-31 13:59:52 +08:00
世界
9779dc0154 Fix test 2022-10-31 13:59:52 +08:00
世界
a2abe31298 Fix uTLS config 2022-10-31 13:59:52 +08:00
世界
930d177dd0 Update dependencies 2022-10-31 13:59:52 +08:00
Fei1Yang
f3d1b59173 Update container action 2022-10-29 18:01:32 +08:00
世界
14452f3049 Update documentation 2022-10-29 18:00:05 +08:00
世界
4119c8647b Update dependencies 2022-10-29 17:56:21 +08:00
世界
90a94a8c63 Improve local dns transport 2022-10-29 17:37:11 +08:00
世界
b0c39ac7ff Suppress no network error 2022-10-28 09:54:04 +08:00
世界
8703e1ff98 Fix decrypt xplus packet 2022-10-28 09:53:57 +08:00
世界
35886b88d7 Add option for custom wireguard reserved bytes 2022-10-28 09:53:57 +08:00
永雏塔菲
d583b35717 Add s390x architecture support
* Update debug.yml

Signed-off-by: 永雏塔菲 <108621198+taffychan@users.noreply.github.com>
2022-10-28 09:53:57 +08:00
Hellojack
217ffb2f95 Update uTLS usage
* Update new uTLS fingerprints

* Update documentation
2022-10-28 09:53:57 +08:00
世界
22f06f582b Fix v2ray api 2022-10-26 20:06:13 +08:00
世界
f2b5098fa0 Update documentation 2022-10-25 21:26:28 +08:00
世界
0ca3290364 Add go1.18 debug build 2022-10-25 21:25:55 +08:00
世界
43d5b8598b Fix shadowtls conn 2022-10-25 21:25:42 +08:00
世界
f3e1d1defc Fix h3 dns transport 2022-10-20 11:04:03 +08:00
世界
95c03c9373 Fix copy pipe 2022-10-20 10:57:57 +08:00
世界
7e0958b4ac Update documentation 2022-10-19 10:55:06 +08:00
Skyxim
6a26737508 Check destination before udp connect 2022-10-19 10:22:46 +08:00
世界
92a92f39c5 Fix naive overflow 2022-10-18 17:52:52 +08:00
世界
fc533cd38d Fix DF for hysteria 2022-10-18 17:27:50 +08:00
世界
68e286499d Update dependencies 2022-10-18 17:27:50 +08:00
世界
f5c1900aad Add message for tfo error 2022-10-18 17:27:50 +08:00
世界
6591dd58ca Remove strict route on windows
replaced by custom route
2022-10-12 16:24:45 +08:00
XYenon
54af113363 Add custom route support (#147) 2022-10-12 16:20:17 +08:00
世界
3f1fe814ef Fix sniff fragmented quic client hello 2022-10-12 16:11:42 +08:00
世界
5a2cebebd1 Remove unused 2022-10-10 14:23:34 +08:00
世界
b8009d61b2 Fix tfo headroom 2022-10-10 13:33:48 +08:00
世界
a61a64bf9e Add shadowtls inbound test 2022-10-10 11:31:03 +08:00
世界
7d17c52fea Add more messages to darwin route error 2022-10-09 21:22:07 +08:00
世界
f5b15b392b Fix ssh outbound 2022-10-09 20:43:01 +08:00
世界
8a53846efd Fix uTLS handshake 2022-10-08 20:31:01 +08:00
世界
badc454452 Fix test 2022-10-08 20:30:52 +08:00
世界
a01bb569d1 Fix websocket headroom 2022-10-08 20:09:36 +08:00
世界
89ff9f8368 Fix interface monitor 2022-10-08 20:09:36 +08:00
世界
7f816a2ebc Add sniff_timeout 2022-10-08 20:09:36 +08:00
世界
39c141651a Update documentation 2022-10-06 23:33:57 +08:00
世界
b0ad9bb6f1 Add shadowtls v2 support 2022-10-06 22:47:11 +08:00
世界
d135d0f287 Update tfo-go usage 2022-10-06 21:58:50 +08:00
世界
b183ccf23d Fix wfp filter weight 2022-10-05 20:24:27 +08:00
世界
c2969bc186 Update documentation 2022-10-03 04:36:54 +08:00
世界
bd86bfcd22 Fix check system stack packet 2022-10-03 04:36:54 +08:00
世界
8aec64b855 Add v2ray mux support for all connections 2022-10-03 04:34:59 +08:00
世界
1445bdba37 Fix trojan fallback 2022-10-01 11:41:15 +08:00
世界
29d08e63b5 Fix clash tracker 2022-10-01 11:29:46 +08:00
世界
1173fdea64 Improve tls writer 2022-10-01 11:29:46 +08:00
世界
968430c338 Minor fixes 2022-09-30 21:08:07 +08:00
世界
3e5bee6faf Fix windows route 2022-09-30 00:36:42 +08:00
世界
aa613cba73 Fix dns close 2022-09-29 09:12:13 +08:00
世界
1e510511ae Fix random seed 2022-09-29 08:49:34 +08:00
世界
1b44faed17 Add v2ray stats api 2022-09-29 08:49:34 +08:00
世界
c7a485815c Add binary to .gitignore 2022-09-26 19:36:51 +08:00
世界
7f9c870bba Add direct io option for clash api 2022-09-26 15:31:02 +08:00
世界
b5564ef3d3 Fix bind control 2022-09-26 13:50:54 +08:00
世界
8ce244dd04 Fix documentation
Signed-off-by: 世界 <i@sekai.icu>
Signed-off-by: unknowndevQwQ <unknowndevQwQ@pm.me>
2022-09-26 12:25:18 +08:00
世界
0f57b93925 Update documentation 2022-09-25 22:29:18 +08:00
世界
c90a77a185 Refine 4in6 processing 2022-09-25 22:29:18 +08:00
世界
c6586f19fa Fix read source address from grpc-go 2022-09-25 22:29:18 +08:00
世界
cbab86ae38 Refine tproxy write back 2022-09-25 22:29:18 +08:00
世界
17b5f031f1 Fix shadowsocks plugins 2022-09-25 16:43:12 +08:00
世界
b00b6b9e25 Fix fqdn socks5 outbound connection 2022-09-25 14:42:39 +08:00
世界
fb6b3b0401 Fix missing source address from transport connection 2022-09-23 18:55:28 +08:00
世界
22ea878fe9 Improve websocket writer 2022-09-23 18:55:07 +08:00
世界
abe3dc6039 Add self sign cert support 2022-09-23 17:13:18 +08:00
世界
852829b9dc Add VMess benchmark result 2022-09-23 16:13:29 +08:00
世界
407509c985 Fix leaks and add test 2022-09-23 13:14:31 +08:00
世界
9856b73cb5 Update documentation 2022-09-23 10:30:07 +08:00
世界
f42356fbcb Fix system stack ipv4 overflow 2022-09-23 10:29:15 +08:00
世界
d0b467671a Merge VLESS to library 2022-09-23 10:28:51 +08:00
世界
c18c545798 Add stdio test 2022-09-23 10:28:24 +08:00
世界
693ef293ac Update buffer usage 2022-09-23 10:27:48 +08:00
世界
a006627795 Disable DF on direct outbound by default 2022-09-23 10:27:46 +08:00
世界
0738b184e4 Fix url test interval 2022-09-23 10:27:42 +08:00
世界
42524ba04e Fix dns sniffer 2022-09-17 16:59:28 +08:00
世界
63fc95b96d Add mux server and XUDP client for VMess 2022-09-17 11:54:04 +08:00
306 changed files with 20033 additions and 6800 deletions

View File

@@ -12,7 +12,7 @@ body:
required: true required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any. - label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true required: true
- label: Yes, I've included all information below (version, config, log, etc). - label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
required: true required: true
- type: textarea - type: textarea

28
.github/renovate.json vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,34 +0,0 @@
name: Test build
on:
pull_request:
branches:
- main
- dev
- dev-next
jobs:
build:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make test

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@
/*.db /*.db
/site/ /site/
/bin/ /bin/
/dist/ /dist/
/sing-box

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
FROM golang:1.19-alpine AS builder FROM golang:1.20-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box
@@ -14,7 +14,6 @@ RUN set -ex \
./cmd/sing-box ./cmd/sing-box
FROM alpine AS dist FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN set -ex \ RUN set -ex \
&& apk upgrade \ && apk upgrade \
&& apk add bash tzdata ca-certificates \ && apk add bash tzdata ca-certificates \

View File

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

View File

@@ -38,3 +38,13 @@ func OutboundTag(detour Outbound) string {
} }
return detour.Tag() return detour.Tag()
} }
type V2RayServer interface {
Service
StatsService() V2RayStatsService
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}

View File

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

View File

@@ -41,7 +41,24 @@ type Router interface {
Rules() []Rule Rules() []Rule
ClashServer() ClashServer ClashServer() ClashServer
SetClashServer(controller ClashServer) SetClashServer(server ClashServer)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
}
type routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
} }
type Rule interface { type Rule interface {
@@ -57,3 +74,7 @@ type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
} }
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

View File

@@ -38,13 +38,25 @@ type myUpstreamHandlerWrapper struct {
} }
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
w.metadata.Destination = metadata.Destination myMetadata := w.metadata
return w.connectionHandler(ctx, conn, w.metadata) if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, myMetadata)
} }
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
w.metadata.Destination = metadata.Destination myMetadata := w.metadata
return w.packetHandler(ctx, conn, w.metadata) if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, myMetadata)
} }
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
@@ -78,13 +90,23 @@ func NewUpstreamContextHandler(
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
myMetadata.Destination = metadata.Destination if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, *myMetadata) return w.connectionHandler(ctx, conn, *myMetadata)
} }
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
myMetadata.Destination = metadata.Destination if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, *myMetadata) return w.packetHandler(ctx, conn, *myMetadata)
} }

View File

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

36
box.go
View File

@@ -31,6 +31,7 @@ type Box struct {
logger log.ContextLogger logger log.ContextLogger
logFile *os.File logFile *os.File
clashServer adapter.ClashServer clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
done chan struct{} done chan struct{}
} }
@@ -39,8 +40,14 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
logOptions := common.PtrValueOrDefault(options.Log) logOptions := common.PtrValueOrDefault(options.Log)
var needClashAPI bool var needClashAPI bool
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" { var needV2RayAPI bool
needClashAPI = true if options.Experimental != nil {
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true
}
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
} }
var logFactory log.Factory var logFactory log.Factory
@@ -90,8 +97,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router, err := route.NewRouter( router, err := route.NewRouter(
ctx, ctx,
logFactory.NewLogger("router"), logFactory,
logFactory.NewLogger("dns"),
common.PtrValueOrDefault(options.Route), common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.DNS),
options.Inbounds, options.Inbounds,
@@ -149,6 +155,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
} }
var clashServer adapter.ClashServer var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
if needClashAPI { if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI)) clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil { if err != nil {
@@ -156,15 +163,23 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
} }
router.SetClashServer(clashServer) router.SetClashServer(clashServer)
} }
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
router.SetV2RayServer(v2rayServer)
}
return &Box{ return &Box{
router: router, router: router,
inbounds: inbounds, inbounds: inbounds,
outbounds: outbounds, outbounds: outbounds,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.NewLogger(""), logger: logFactory.Logger(),
logFile: logFile, logFile: logFile,
clashServer: clashServer, clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}), done: make(chan struct{}),
}, nil }, nil
} }
@@ -223,6 +238,12 @@ func (s *Box) start() error {
return E.Cause(err, "start clash api server") return E.Cause(err, "start clash api server")
} }
} }
if s.v2rayServer != nil {
err = s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil return nil
} }
@@ -244,6 +265,11 @@ func (s *Box) Close() error {
s.router, s.router,
s.logFactory, s.logFactory,
s.clashServer, s.clashServer,
s.v2rayServer,
common.PtrOrNil(s.logFile), common.PtrOrNil(s.logFile),
) )
} }
func (s *Box) Router() adapter.Router {
return s.router
}

View File

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

81
cmd/internal/build/sdk.go Normal file
View File

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

View File

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

210
common/badtls/badtls.go Normal file
View File

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

View File

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

13
common/badtls/conn.go Normal file
View File

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

26
common/badtls/link.go Normal file
View File

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

View File

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

141
common/dialer/tfo.go Normal file
View File

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

View File

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

View File

@@ -329,6 +329,23 @@ func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
return c.ExtendedConn.Write(b) return c.ExtendedConn.Write(b)
} }
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error { func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
if !c.requestWrite { if !c.requestWrite {
defer buffer.Release() defer buffer.Release()
@@ -343,6 +360,11 @@ func (c *ClientPacketConn) FrontHeadroom() int {
return 2 return 2
} }
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = c.ReadBuffer(buffer)
return
}
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return c.WriteBuffer(buffer) return c.WriteBuffer(buffer)
} }
@@ -466,10 +488,7 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
if err != nil { if err != nil {
return return
} }
if buffer.FreeLen() < int(length) { _, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return destination, io.ErrShortBuffer
}
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
return return
} }

View File

@@ -43,7 +43,7 @@ func ParseProtocol(name string) (Protocol, error) {
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) { func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
switch p { switch p {
case ProtocolSMux: case ProtocolSMux:
session, err := smux.Server(conn, nil) session, err := smux.Server(conn, smuxConfig())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,7 +58,7 @@ func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) { func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
switch p { switch p {
case ProtocolSMux: case ProtocolSMux:
session, err := smux.Client(conn, nil) session, err := smux.Client(conn, smuxConfig())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,6 +70,12 @@ func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
} }
} }
func smuxConfig() *smux.Config {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
return config
}
func yaMuxConfig() *yamux.Config { func yaMuxConfig() *yamux.Config {
config := yamux.DefaultConfig() config := yamux.DefaultConfig()
config.LogOutput = io.Discard config.LogOutput = io.Discard

View File

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

View File

@@ -28,11 +28,5 @@ type Info struct {
} }
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := findProcessInfo(searcher, ctx, network, source, destination) return 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

@@ -5,6 +5,8 @@ import (
"encoding/binary" "encoding/binary"
"net/netip" "net/netip"
"os" "os"
"strconv"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -29,6 +31,22 @@ func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, so
return &Info{ProcessPath: processName, UserId: -1}, nil return &Info{ProcessPath: processName, UserId: -1}, nil
} }
var structSize = func() int {
value, _ := syscall.Sysctl("kern.osrelease")
major, _, _ := strings.Cut(value, ".")
n, _ := strconv.ParseInt(major, 10, 64)
switch true {
case n >= 22:
return 408
default:
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
return 384
}
}()
func findProcessName(network string, ip netip.Addr, port int) (string, error) { func findProcessName(network string, ip netip.Addr, port int) (string, error) {
var spath string var spath string
switch network { switch network {
@@ -53,7 +71,7 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// size/offset are round up (aligned) to 8 bytes in darwin // size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384 itemSize := structSize
if network == N.NetworkTCP { if network == N.NetworkTCP {
// rup8(sizeof(xtcpcb_n)) // rup8(sizeof(xtcpcb_n))
itemSize += 208 itemSize += 208

View File

@@ -36,8 +36,8 @@ func (l *Listener) Accept() (net.Conn, error) {
} }
if header != nil { if header != nil {
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{ return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr), Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
Destination: M.SocksaddrFromNet(header.DestinationAddr), Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
}}, nil }}, nil
} }
return conn, nil return conn, nil

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
@@ -49,5 +50,8 @@ func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
return nil, os.ErrInvalid
}
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil
} }

View File

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

View File

@@ -24,8 +24,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil { if err != nil {
return nil, err return nil, err
} }
if typeByte&0x40 == 0 {
if typeByte&0x80 == 0 || typeByte&0x40 == 0 {
return nil, E.New("bad type byte") return nil, E.New("bad type byte")
} }
var versionNumber uint32 var versionNumber uint32
@@ -145,9 +144,6 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
default: default:
return nil, E.New("bad packet number length") return nil, E.New("bad packet number length")
} }
if packetNumber != 0 {
return nil, E.New("bad packet number: ", packetNumber)
}
extHdrLen := hdrLen + int(packetNumberLength) extHdrLen := hdrLen + int(packetNumberLength)
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:]) copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
data := newPacket[extHdrLen : int(packetLen)+hdrLen] data := newPacket[extHdrLen : int(packetLen)+hdrLen]
@@ -172,37 +168,76 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil { if err != nil {
return nil, err return nil, err
} }
var frameType byte
var frameLen uint64
var fragments []struct {
offset uint64
length uint64
payload []byte
}
decryptedReader := bytes.NewReader(decrypted) decryptedReader := bytes.NewReader(decrypted)
frameType, err := decryptedReader.ReadByte() for {
if err != nil {
return nil, err
}
for frameType == 0x0 {
// skip padding
frameType, err = decryptedReader.ReadByte() frameType, err = decryptedReader.ReadByte()
if err != nil { if err == io.EOF {
return nil, err break
}
switch frameType {
case 0x0:
continue
case 0x1:
continue
case 0x6:
var offset uint64
offset, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
}
var length uint64
length, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
}
index := len(decrypted) - decryptedReader.Len()
fragments = append(fragments, struct {
offset uint64
length uint64
payload []byte
}{offset, length, decrypted[index : index+int(length)]})
frameLen += length
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)
if err != nil {
return nil, err
}
default:
// ignore unknown frame type
} }
}
if frameType != 0x6 {
// not crypto frame
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, nil
}
_, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return nil, err
}
_, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
return nil, err
} }
tlsHdr := make([]byte, 5) tlsHdr := make([]byte, 5)
tlsHdr[0] = 0x16 tlsHdr[0] = 0x16
binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303)) binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303))
binary.BigEndian.PutUint16(tlsHdr[3:], uint16(decryptedReader.Len())) binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen))
metadata, err := TLSClientHello(ctx, io.MultiReader(bytes.NewReader(tlsHdr), decryptedReader)) var index uint64
var length int
var readers []io.Reader
readers = append(readers, bytes.NewReader(tlsHdr))
find:
for {
for _, fragment := range fragments {
if fragment.offset == index {
readers = append(readers, bytes.NewReader(fragment.payload))
index = fragment.offset + fragment.length
length++
continue find
}
}
if length == len(fragments) {
break
}
return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments")
}
metadata, err := TLSClientHello(ctx, io.MultiReader(readers...))
if err != nil { if err != nil {
return nil, err return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
} }
metadata.Protocol = C.ProtocolQUIC metadata.Protocol = C.ProtocolQUIC
return metadata, nil return metadata, nil

View File

@@ -19,6 +19,15 @@ func TestSniffQUICv1(t *testing.T) {
require.Equal(t, metadata.Domain, "cloudflare-quic.com") require.Equal(t, metadata.Domain, "cloudflare-quic.com")
} }
func TestSniffQUICFragment(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("cc00000001082e3d5d1b64040c55000044d0ccea69e773f6631c1d18b04ae9ee75fcfc34ef74fa62533c93534338a86f101a05d70e0697fb483063fa85db1c59ccfbda5c35234931d8524d8aac37eaaad649470a67794cd754b23c98695238b8363452333bc8c4858376b4166e001da2006e35cf98a91e11a56419b2786775284942d0f7163982f7c248867d12dd374957481dbc564013ff785e1916195eef671f725908f761099d992d69231336ba81d9e25fe2fa3a6eff4318a6ccf10176fc841a1b315f7b35c5b292266fc869d76ca533e7d14e86d82db2e22eacd350977e47d2e012d8a5891c5aaf2a0f4c2b2dae897c161e5b68cbb4dee952472bdc1e21504b8f02534ec4366ce3f8bf86efc78e0232778fbd554457567112abdcafcf6d4d8fcf35083c25d9495679614aba21696e338c62b585046cc55ba8c09c844361d889a47c3ea703b4e23545a9ab2c0bb369693a9ddfb5daffa85cf80fdd6ad66738664e5b0a551729b4955cff7255afcb04dee88c2f072c9de7400947a1bd9327ac5d012a33000ada021d4c03d249fb017d6ac9200b2f9436beab8183ddfbe2d8aee31ffb7df9e1cc181c1af80c39a89965d18ed12da8e3ebe2ae1fbe4b348f83ba19e3e3d1c9b22bcf03ab6ad9b30fe180623faa291ebad83bcd71d7b57f2f5e2f3b8e81d24fb70b2f2159239e8f21ffafef2747aba47d97ab4081e603c018b10678cf99cab1fb42156a14486fa435153979d7279fd22cd40af7088bfc7eff41af2f4b3c0c8864d0040d74dff427f7bffdb8c278474ea00311326cf4925471a8cf596cb92119f19e0f789490ba9cb77b98015a987d93e0324cf1a38b55109f00c3e6ddc5180fb107bf468323afec9bb49fd6a86418569789d66cafe3b8253c2aebb3af3782c1c54dd560487d031d28e6a6e23e159581bb1d47efc4da3fe1d169f9ffb0ca9ba61af0a38a92fde5bc5e6ec026e8378a6315a7b95abf1d2da790a391306ce74d0baf8e2ce648ca74c487f2c0a76a28a80cdf5bd34316eb607684fe7e6d9e83824a00e07660d0b90e3cddd61ebf10748263474afa88c300549e64ce2e90560bb1a12dee7e9484f729a8a4ee7c5651adb5194b3b3ae38e501567c7dbf36e7bb37a2c20b74655f47f2d9af18e52e9d4c9c9eee8e63745779b8f0b06f3a09d846ba62eb978ad77c85de1ee2fee3fbb4c2d283c73e1ccba56a4658e48a2665d200f7f9342f8e84c2ba490094a4f94feec89e42d2f654f564c2beb2997bafa1fc2c68ad8e160b63587d49abc31b834878d52acfb05fb73d0e059b206162e3c90b40c4bc08407ffcb3c08431895b691a3fea923f1f3b48db75d3e6b91fd319ffe4d486e0e14bd5c6affc838dee63d9e0b80f169b5e6c02c7321dcb20deb2b8e707b60e345a308d505bbf26a93d8f18b39d62632e9a77cbe48b3b32eb8819d6311a49820d40f5acbf0273c91c36b2269a03e72ee64df3dfb10ddefe73c64ef60870b2b77bd99dea655f5fe791b538a929a14d99f6d69685d72431ea5f0f4b27a044f2f575ab474fcc3857895934de1ca2581798eaef2c17fe5aaf2e6add97fa32997c7026f15c1b1ad0e6043ae506027a7c0242546fdc851cca39a204e56879f2cef838be8ec66e0f2292f8c862e06f810eb9b80c7a467ce6e90155206352c7f82b1173ba3b98d35bb72c259a60db20dd1a43fe6d7aef0265e6eaa5caafd9b64b448ff745a2046acbdb65cf2a5007809808a4828dc99097feedc734c236260c584")
require.NoError(t, err)
metadata, err := sniff.QUICClientHello(context.Background(), pkt)
require.NoError(t, err)
require.Equal(t, metadata.Domain, "cloudflare-quic.com")
}
func FuzzSniffQUIC(f *testing.F) { func FuzzSniffQUIC(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
sniff.QUICClientHello(context.Background(), data) sniff.QUICClientHello(context.Background(), data)

View File

@@ -5,7 +5,6 @@ import (
"context" "context"
"io" "io"
"net" "net"
"os"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -19,8 +18,11 @@ type (
PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error) PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error)
) )
func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, sniffers ...StreamSniffer) (*adapter.InboundContext, error) { func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) (*adapter.InboundContext, error) {
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout)) if timeout == 0 {
timeout = C.ReadPayloadTimeout
}
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -30,23 +32,25 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, sniffers
return nil, err return nil, err
} }
var metadata *adapter.InboundContext var metadata *adapter.InboundContext
var errors []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes())) metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes()))
if err != nil { if metadata != nil {
continue return metadata, nil
} }
return metadata, nil errors = append(errors, err)
} }
return nil, os.ErrInvalid return nil, E.Errors(errors...)
} }
func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) { func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) {
var errors []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
sniffMetadata, err := sniffer(ctx, packet) metadata, err := sniffer(ctx, packet)
if err != nil { if metadata != nil {
continue return metadata, nil
} }
return sniffMetadata, nil errors = append(errors, err)
} }
return nil, os.ErrInvalid return nil, E.Errors(errors...)
} }

View File

@@ -5,6 +5,7 @@ package tls
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"os"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -13,6 +14,8 @@ import (
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/acme" "github.com/mholt/acmez/acme"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
type acmeWrapper struct { type acmeWrapper struct {
@@ -54,6 +57,11 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
config := &certmagic.Config{ config := &certmagic.Config{
DefaultServerName: options.DefaultServerName, DefaultServerName: options.DefaultServerName,
Storage: storage, Storage: storage,
Logger: zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
} }
acmeConfig := certmagic.ACMEIssuer{ acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer, CA: acmeServer,
@@ -63,8 +71,9 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort), AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort), AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: config.Logger,
} }
if options.ExternalAccount != nil { if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount) acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
} }
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)} config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}

View File

@@ -2,10 +2,12 @@ package tls
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -13,6 +15,9 @@ import (
) )
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
}
config, err := NewClient(router, serverAddress, options) config, err := NewClient(router, serverAddress, options)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -21,12 +26,15 @@ func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress
} }
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
if !options.Enabled {
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled { if options.ECH != nil && options.ECH.Enabled {
return newECHClient(router, serverAddress, options) return NewECHClient(router, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled { } else if options.UTLS != nil && options.UTLS.Enabled {
return newUTLSClient(router, serverAddress, options) return NewUTLSClient(router, serverAddress, options)
} else { } else {
return newStdClient(serverAddress, options) return NewSTDClient(serverAddress, options)
} }
} }
@@ -35,7 +43,17 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel() defer cancel()
err := tlsConn.HandshakeContext(ctx) err := tlsConn.HandshakeContext(ctx)
return tlsConn, err if err != nil {
return nil, err
}
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
var badConn badtls.TLSConn
badConn, err = badtls.Create(stdConn)
if err == nil {
return badConn, nil
}
}
return tlsConn, nil
} }
type Dialer struct { type Dialer struct {

View File

@@ -10,15 +10,19 @@ import (
) )
type ( type (
STDConfig = tls.Config STDConfig = tls.Config
STDConn = tls.Conn STDConn = tls.Conn
ConnectionState = tls.ConnectionState
) )
type Config interface { type Config interface {
ServerName() string
SetServerName(serverName string)
NextProtos() []string NextProtos() []string
SetNextProtos(nextProto []string) SetNextProtos(nextProto []string)
Config() (*STDConfig, error) Config() (*STDConfig, error)
Client(conn net.Conn) Conn Client(conn net.Conn) Conn
Clone() Config
} }
type ServerConfig interface { type ServerConfig interface {
@@ -30,7 +34,7 @@ type ServerConfig interface {
type Conn interface { type Conn interface {
net.Conn net.Conn
HandshakeContext(ctx context.Context) error HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState ConnectionState() ConnectionState
} }
func ParseTLSVersion(version string) (uint16, error) { func ParseTLSVersion(version string) (uint16, error) {

View File

@@ -11,35 +11,49 @@ import (
"net/netip" "net/netip"
"os" "os"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
cftls "github.com/sagernet/sing-box/transport/cloudflaretls"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
type echClientConfig struct { type ECHClientConfig struct {
config *cftls.Config config *cftls.Config
} }
func (e *echClientConfig) NextProtos() []string { func (e *ECHClientConfig) ServerName() string {
return e.config.ServerName
}
func (e *ECHClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (e *ECHClientConfig) NextProtos() []string {
return e.config.NextProtos return e.config.NextProtos
} }
func (e *echClientConfig) SetNextProtos(nextProto []string) { func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
e.config.NextProtos = nextProto e.config.NextProtos = nextProto
} }
func (e *echClientConfig) Config() (*STDConfig, error) { func (e *ECHClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for ECH") return nil, E.New("unsupported usage for ECH")
} }
func (e *echClientConfig) Client(conn net.Conn) Conn { func (e *ECHClientConfig) Client(conn net.Conn) Conn {
return &echConnWrapper{cftls.Client(conn, e.config)} return &echConnWrapper{cftls.Client(conn, e.config)}
} }
func (e *ECHClientConfig) Clone() Config {
return &ECHClientConfig{
config: e.config.Clone(),
}
}
type echConnWrapper struct { type echConnWrapper struct {
*cftls.Conn *cftls.Conn
} }
@@ -62,7 +76,7 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
} }
} }
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string var serverName string
if options.ServerName != "" { if options.ServerName != "" {
serverName = options.ServerName serverName = options.ServerName
@@ -162,11 +176,9 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
} else { } else {
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router) tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
} }
return &echClientConfig{&tlsConfig}, nil return &ECHClientConfig{&tlsConfig}, nil
} }
const typeHTTPS = 65
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) { func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) { return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
message := &mDNS.Msg{ message := &mDNS.Msg{

View File

@@ -8,6 +8,6 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`) return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
} }

50
common/tls/mkcert.go Normal file
View File

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

View File

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

View File

@@ -11,11 +11,39 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
type stdClientConfig struct { type STDClientConfig struct {
config *tls.Config config *tls.Config
} }
func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) { func (s *STDClientConfig) ServerName() string {
return s.config.ServerName
}
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *STDClientConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, s.config)
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string var serverName string
if options.ServerName != "" { if options.ServerName != "" {
serverName = options.ServerName serverName = options.ServerName
@@ -96,21 +124,5 @@ func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Conf
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
return &stdClientConfig{&tlsConfig}, nil return &STDClientConfig{&tlsConfig}, nil
}
func (s *stdClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (s *stdClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (s *stdClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *stdClientConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, s.config)
} }

View File

@@ -15,6 +15,8 @@ import (
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct { type STDServerConfig struct {
config *tls.Config config *tls.Config
logger log.Logger logger log.Logger
@@ -26,6 +28,14 @@ type STDServerConfig struct {
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
} }
func (c *STDServerConfig) ServerName() string {
return c.config.ServerName
}
func (c *STDServerConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
}
func (c *STDServerConfig) NextProtos() []string { func (c *STDServerConfig) NextProtos() []string {
return c.config.NextProtos return c.config.NextProtos
} }
@@ -34,97 +44,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto c.config.NextProtos = nextProto
} }
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
var tlsConfig *tls.Config
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
if err != nil {
return nil, err
}
} else {
tlsConfig = &tls.Config{}
}
if options.ServerName != "" {
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
var key []byte
if acmeService == nil {
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if options.Key != "" {
key = []byte(options.Key)
} else if options.KeyPath != "" {
content, err := os.ReadFile(options.KeyPath)
if err != nil {
return nil, E.Cause(err, "read key")
}
key = content
}
if certificate == nil {
return nil, E.New("missing certificate")
}
if key == nil {
return nil, E.New("missing key")
}
keyPair, err := tls.X509KeyPair(certificate, key)
if err != nil {
return nil, E.Cause(err, "parse x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
return &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
}, nil
}
func (c *STDServerConfig) Config() (*STDConfig, error) { func (c *STDServerConfig) Config() (*STDConfig, error) {
return c.config, nil return c.config, nil
} }
@@ -137,6 +56,12 @@ func (c *STDServerConfig) Server(conn net.Conn) Conn {
return tls.Server(conn, c.config) return tls.Server(conn, c.config)
} }
func (c *STDServerConfig) Clone() Config {
return &STDServerConfig{
config: c.config.Clone(),
}
}
func (c *STDServerConfig) Start() error { func (c *STDServerConfig) Start() error {
if c.acmeService != nil { if c.acmeService != nil {
return c.acmeService.Start() return c.acmeService.Start()
@@ -230,3 +155,104 @@ func (c *STDServerConfig) Close() error {
} }
return nil return nil
} }
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
var tlsConfig *tls.Config
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
//nolint:staticcheck
if err != nil {
return nil, err
}
if options.Insecure {
return nil, errInsecureUnused
}
} else {
tlsConfig = &tls.Config{}
}
if options.ServerName != "" {
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
var key []byte
if acmeService == nil {
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if options.Key != "" {
key = []byte(options.Key)
} else if options.KeyPath != "" {
content, err := os.ReadFile(options.KeyPath)
if err != nil {
return nil, E.Cause(err, "read key")
}
key = content
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(info.ServerName)
}
} else {
if certificate == nil {
return nil, E.New("missing certificate")
} else if key == nil {
return nil, E.New("missing key")
}
keyPair, err := tls.X509KeyPair(certificate, key)
if err != nil {
return nil, E.Cause(err, "parse x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
return &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
}, nil
}

View File

@@ -3,7 +3,6 @@
package tls package tls
import ( import (
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"net" "net"
@@ -17,35 +16,39 @@ import (
utls "github.com/refraction-networking/utls" utls "github.com/refraction-networking/utls"
) )
type utlsClientConfig struct { type UTLSClientConfig struct {
config *utls.Config config *utls.Config
id utls.ClientHelloID id utls.ClientHelloID
} }
func (e *utlsClientConfig) NextProtos() []string { func (e *UTLSClientConfig) ServerName() string {
return e.config.ServerName
}
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (e *UTLSClientConfig) NextProtos() []string {
return e.config.NextProtos return e.config.NextProtos
} }
func (e *utlsClientConfig) SetNextProtos(nextProto []string) { func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
e.config.NextProtos = nextProto e.config.NextProtos = nextProto
} }
func (e *utlsClientConfig) Config() (*STDConfig, error) { func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS") return nil, E.New("unsupported usage for uTLS")
} }
func (e *utlsClientConfig) Client(conn net.Conn) Conn { func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
return &utlsConnWrapper{utls.UClient(conn, e.config, e.id)} return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
} }
type utlsConnWrapper struct { type utlsConnWrapper struct {
*utls.UConn *utls.UConn
} }
func (c *utlsConnWrapper) HandshakeContext(ctx context.Context) error {
return c.Conn.Handshake()
}
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState { func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState() state := c.Conn.ConnectionState()
return tls.ConnectionState{ return tls.ConnectionState{
@@ -64,7 +67,14 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
} }
} }
func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string var serverName string
if options.ServerName != "" { if options.ServerName != "" {
serverName = options.ServerName serverName = options.ServerName
@@ -140,12 +150,22 @@ func newUTLSClient(router adapter.Router, serverAddress string, options option.O
id = utls.HelloChrome_Auto id = utls.HelloChrome_Auto
case "firefox": case "firefox":
id = utls.HelloFirefox_Auto id = utls.HelloFirefox_Auto
case "edge":
id = utls.HelloEdge_Auto
case "safari":
id = utls.HelloSafari_Auto
case "360":
id = utls.Hello360_Auto
case "qq":
id = utls.HelloQQ_Auto
case "ios": case "ios":
id = utls.HelloIOS_Auto id = utls.HelloIOS_Auto
case "android": case "android":
id = utls.HelloAndroid_11_OkHttp id = utls.HelloAndroid_11_OkHttp
case "random": case "random":
id = utls.HelloRandomized id = utls.HelloRandomized
default:
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
} }
return &utlsClientConfig{&tlsConfig, id}, nil return &UTLSClientConfig{&tlsConfig, id}, nil
} }

View File

@@ -8,6 +8,6 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`) return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
} }

View File

@@ -1,145 +0,0 @@
package trafficcontrol
import (
"io"
"net"
"sync"
"sync/atomic"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Manager[U comparable] struct {
access sync.Mutex
users map[U]*Traffic
}
type Traffic struct {
Upload uint64
Download uint64
}
func NewManager[U comparable]() *Manager[U] {
return &Manager[U]{
users: make(map[U]*Traffic),
}
}
func (m *Manager[U]) Reset() {
m.users = make(map[U]*Traffic)
}
func (m *Manager[U]) TrackConnection(user U, conn net.Conn) net.Conn {
m.access.Lock()
defer m.access.Unlock()
var traffic *Traffic
if t, loaded := m.users[user]; loaded {
traffic = t
} else {
traffic = new(Traffic)
m.users[user] = traffic
}
return &TrackConn{conn, traffic}
}
func (m *Manager[U]) TrackPacketConnection(user U, conn N.PacketConn) N.PacketConn {
m.access.Lock()
defer m.access.Unlock()
var traffic *Traffic
if t, loaded := m.users[user]; loaded {
traffic = t
} else {
traffic = new(Traffic)
m.users[user] = traffic
}
return &TrackPacketConn{conn, traffic}
}
func (m *Manager[U]) ReadTraffics() map[U]Traffic {
m.access.Lock()
defer m.access.Unlock()
trafficMap := make(map[U]Traffic)
for user, traffic := range m.users {
upload := atomic.SwapUint64(&traffic.Upload, 0)
download := atomic.SwapUint64(&traffic.Download, 0)
if upload == 0 && download == 0 {
continue
}
trafficMap[user] = Traffic{
Upload: upload,
Download: download,
}
}
return trafficMap
}
type TrackConn struct {
net.Conn
*Traffic
}
func (c *TrackConn) Read(p []byte) (n int, err error) {
n, err = c.Conn.Read(p)
if n > 0 {
atomic.AddUint64(&c.Upload, uint64(n))
}
return
}
func (c *TrackConn) Write(p []byte) (n int, err error) {
n, err = c.Conn.Write(p)
if n > 0 {
atomic.AddUint64(&c.Download, uint64(n))
}
return
}
func (c *TrackConn) WriteTo(w io.Writer) (n int64, err error) {
n, err = bufio.Copy(w, c.Conn)
if n > 0 {
atomic.AddUint64(&c.Upload, uint64(n))
}
return
}
func (c *TrackConn) ReadFrom(r io.Reader) (n int64, err error) {
n, err = bufio.Copy(c.Conn, r)
if n > 0 {
atomic.AddUint64(&c.Download, uint64(n))
}
return
}
func (c *TrackConn) Upstream() any {
return c.Conn
}
type TrackPacketConn struct {
N.PacketConn
*Traffic
}
func (c *TrackPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
destination, err := c.PacketConn.ReadPacket(buffer)
if err == nil {
atomic.AddUint64(&c.Upload, uint64(buffer.Len()))
}
return destination, err
}
func (c *TrackPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
n := buffer.Len()
err := c.PacketConn.WritePacket(buffer, destination)
if err == nil {
atomic.AddUint64(&c.Download, uint64(n))
}
return err
}
func (c *TrackPacketConn) Upstream() any {
return c.PacketConn
}

8
constant/dhcp.go Normal file
View File

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

View File

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

View File

@@ -1,3 +1,3 @@
package constant package constant
var Version = "1.1-beta6" var Version = "1.2-beta2"

View File

@@ -1,3 +1,242 @@
#### 1.2-beta2
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add fallback support for v2ray transport
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
#### 1.2-beta1
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add v2ray [user stats](/configuration/experimental#statsusers) api
* Add new clash DNS query api
* Improve vmess request
* Fix ipv6 redirect on Linux
* Fix match geoip private
#### 1.1.5
* Add Go 1.20 support
* Fix inbound default DF value
* Fix auth_user route for naive inbound
* Fix gRPC lite header
* Ignore domain case in route rules
#### 1.1.4
* Fix DNS log
* Fix write to h2 conn after closed
* Fix create UDP DNS transport from plain IPv6 address
#### 1.1.2
* Fix http proxy auth
* Fix user from stream packet conn
* Fix DNS response TTL
* Fix override packet conn
* Skip override system proxy bypass list
* Improve DNS log
#### 1.1.1
* Fix acme config
* Fix vmess packet conn
* Suppress quic-go set DF error
#### 1.1
* Fix close clash cache
Important changes since 1.0:
* Add support for use with android VPNService
* Add tun support for WireGuard outbound
* Add system tun stack
* Add comment filter for config
* Add option for allow optional proxy protocol header
* Add Clash mode and persistence support
* Add TLS ECH and uTLS support for outbound TLS options
* Add internal simple-obfs and v2ray-plugin
* Add ShadowsocksR outbound
* Add VLESS outbound and XUDP
* Skip wait for hysteria tcp handshake response
* Add v2ray mux support for all inbound
* Add XUDP support for VMess
* Improve websocket writer
* Refine tproxy write back
* Fix DNS leak caused by
Windows' ordinary multihomed DNS resolution behavior
* Add sniff_timeout listen option
* Add custom route support for tun
* Add option for custom wireguard reserved bytes
* Split bind_address into ipv4 and ipv6
* Add ShadowTLS v1 and v2 support
#### 1.1-rc1
* Fix TLS config for h2 server
* Fix crash when input bad method in shadowsocks multi-user inbound
* Fix listen UDP
* Fix check invalid packet on macOS
#### 1.1-beta18
* Enhance defense against active probe for shadowtls server **1**
**1**:
The `fallback_after` option has been removed.
#### 1.1-beta17
* Fix shadowtls server **1**
*1*:
Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
#### 1.0.7
* Add support for new x/h2 deadline
* Fix copy pipe
* Fix decrypt xplus packet
* Fix macOS Ventura process name match
* Fix smux keepalive
* Fix vmess request buffer
* Fix h2c transport
* Fix tor geoip
* Fix udp connect for mux client
* Fix default dns transport strategy
#### 1.1-beta16
* Improve shadowtls server
* Fix default dns transport strategy
* Update uTLS to v1.2.0
#### 1.1-beta15
* Add support for new x/h2 deadline
* Fix udp connect for mux client
* Fix dns buffer
* Fix quic dns retry
* Fix create TLS config
* Fix websocket alpn
* Fix tor geoip
#### 1.1-beta14
* Add multi-user support for hysteria inbound **1**
* Add custom tls client support for std grpc
* Fix smux keep alive
* Fix vmess request buffer
* Fix default local DNS server behavior
* Fix h2c transport
*1*:
The `auth` and `auth_str` fields have been replaced by the `users` field.
#### 1.1-beta13
* Add custom worker count option for WireGuard outbound
* Split bind_address into ipv4 and ipv6
* Move WFP manipulation to strict route
* Fix WireGuard outbound panic when close
* Fix macOS Ventura process name match
* Fix QUIC connection migration by @HyNetwork
* Fix handling QUIC client SNI by @HyNetwork
#### 1.1-beta12
* Fix uTLS config
* Update quic-go to v0.30.0
* Update cloudflare-tls to go1.18.7
#### 1.1-beta11
* Add option for custom wireguard reserved bytes
* Fix shadowtls v2
* Fix h3 dns transport
* Fix copy pipe
* Fix decrypt xplus packet
* Fix v2ray api
* Suppress no network error
* Improve local dns transport
#### 1.1-beta10
* Add [sniff_timeout](/configuration/shared/listen#sniff_timeout) listen option
* Add [custom route](/configuration/inbound/tun#inet4_route_address) support for tun **1**
* Fix interface monitor
* Fix websocket headroom
* Fix uTLS handshake
* Fix ssh outbound
* Fix sniff fragmented quic client hello
* Fix DF for hysteria
* Fix naive overflow
* Check destination before udp connect
* Update uTLS to v1.1.5
* Update tfo-go to v2.0.2
* Update fsnotify to v1.6.0
* Update grpc to v1.50.1
*1*:
The `strict_route` on windows is removed.
#### 1.0.6
* Fix ssh outbound
* Fix sniff fragmented quic client hello
* Fix naive overflow
* Check destination before udp connect
#### 1.1-beta9
* Fix windows route **1**
* Add [v2ray statistics api](/configuration/experimental#v2ray-api-fields)
* Add ShadowTLS v2 support **2**
* Fixes and improvements
**1**:
* Fix DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
* Flush Windows DNS cache when start/close
**2**:
See [ShadowTLS inbound](/configuration/inbound/shadowtls#version)
and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
#### 1.1-beta8
* Fix leaks on close
* Improve websocket writer
* Refine tproxy write back
* Refine 4in6 processing
* Fix shadowsocks plugins
* Fix missing source address from transport connection
* Fix fqdn socks5 outbound connection
* Fix read source address from grpc-go
#### 1.0.5
* Fix missing source address from transport connection
* Fix fqdn socks5 outbound connection
* Fix read source address from grpc-go
#### 1.1-beta7
* Add v2ray mux and XUDP support for VMess inbound
* Add XUDP support for VMess outbound
* Disable DF on direct outbound by default
* Fix bugs in 1.1-beta6
#### 1.1-beta6 #### 1.1-beta6
* Add [URLTest outbound](/configuration/outbound/urltest) * Add [URLTest outbound](/configuration/outbound/urltest)

View File

@@ -9,6 +9,11 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -119,6 +124,10 @@ Tags of [Inbound](/configuration/inbound).
Not limited if empty. Not limited if empty.
#### query_type
DNS query type. Values can be integers or type name strings.
#### network #### network
`tcp` or `udp`. `tcp` or `udp`.

View File

@@ -9,6 +9,11 @@
"mixed-in" "mixed-in"
], ],
"ip_version": 6, "ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp", "network": "tcp",
"auth_user": [ "auth_user": [
"usera", "usera",
@@ -118,6 +123,10 @@
默认不限制。 默认不限制。
#### query_type
DNS 查询类型。值可以为整数或者类型名称字符串。
#### network #### network
`tcp``udp` `tcp``udp`

View File

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

View File

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

View File

@@ -12,21 +12,37 @@
"default_mode": "rule", "default_mode": "rule",
"store_selected": false, "store_selected": false,
"cache_file": "cache.db" "cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! note ""
Traffic statistics and connection management can degrade performance.
### Clash API Fields ### Clash API Fields
!!! error "" !!! error ""
Clash API is not included by default, see [Installation](/#installation). Clash API is not included by default, see [Installation](/#installation).
!!! note ""
Traffic statistics and connection management will disable TCP splice in linux and reduce performance, use at your own risk.
#### external_controller #### external_controller
RESTful web API listening address. Clash API will be disabled if empty. RESTful web API listening address. Clash API will be disabled if empty.
@@ -59,4 +75,34 @@ Store selected outbound for the `Selector` outbound in cache file.
#### cache_file #### cache_file
Cache file path, `cache.db` will be used if empty. Cache file path, `cache.db` will be used if empty.
### V2Ray API Fields
!!! error ""
V2Ray API is not included by default, see [Installation](/#installation).
#### listen
gRPC API listening address. V2Ray API will be disabled if empty.
#### stats
Traffic statistics service settings.
#### stats.enabled
Enable statistics service.
#### stats.inbounds
Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@@ -12,21 +12,37 @@
"default_mode": "rule", "default_mode": "rule",
"store_selected": false, "store_selected": false,
"cache_file": "cache.db" "cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
} }
} }
} }
``` ```
!!! note ""
流量统计和连接管理会降低性能。
### Clash API 字段 ### Clash API 字段
!!! error "" !!! error ""
默认安装不包含 Clash API参阅 [安装](/zh/#_2)。 默认安装不包含 Clash API参阅 [安装](/zh/#_2)。
!!! note ""
流量统计和连接管理将禁用 Linux 中的 TCP splice 并降低性能,使用风险自负。
#### external_controller #### external_controller
RESTful web API 监听地址。如果为空,则禁用 Clash API。 RESTful web API 监听地址。如果为空,则禁用 Clash API。
@@ -57,4 +73,34 @@ Clash 中的默认模式,默认使用 `rule`。
#### cache_file #### cache_file
缓存文件路径,默认使用`cache.db` 缓存文件路径,默认使用`cache.db`
### V2Ray API 字段
!!! error ""
默认安装不包含 V2Ray API参阅 [安装](/zh/#_2)。
#### listen
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
#### stats
流量统计服务设置。
#### stats.enabled
启用统计服务。
#### stats.inbounds
统计流量的入站列表。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,13 +8,20 @@
{ {
"type": "tun", "type": "tun",
"tag": "tun-in", "tag": "tun-in",
"interface_name": "tun0", "interface_name": "tun0",
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000, "mtu": 9000,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
],
"inet6_route_address": [
"::/1",
"8000::/1"
],
"endpoint_independent_nat": false, "endpoint_independent_nat": false,
"stack": "system", "stack": "system",
"include_uid": [ "include_uid": [
@@ -39,8 +46,8 @@
"exclude_package": [ "exclude_package": [
"com.android.captiveportallogin" "com.android.captiveportallogin"
], ],
...
... // Listen Fields // Listen Fields
} }
``` ```
@@ -86,7 +93,9 @@ Set the default route to the Tun.
#### strict_route #### strict_route
Enforce strict routing rules in Linux when `auto_route` is enabled: Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable * Let unsupported network unreachable
* Route all connections to tun * Route all connections to tun
@@ -94,6 +103,21 @@ Enforce strict routing rules in Linux when `auto_route` is enabled:
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
not be accessible by others. not be accessible by others.
*In Windows*:
* Add firewall rules to prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
#### inet4_route_address
Use custom routes instead of default when `auto_route` is enabled.
#### inet6_route_address
Use custom routes instead of default when `auto_route` is enabled.
#### endpoint_independent_nat #### endpoint_independent_nat
!!! info "" !!! info ""

View File

@@ -8,13 +8,20 @@
{ {
"type": "tun", "type": "tun",
"tag": "tun-in", "tag": "tun-in",
"interface_name": "tun0", "interface_name": "tun0",
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000, "mtu": 9000,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
],
"inet6_route_address": [
"::/1",
"8000::/1"
],
"endpoint_independent_nat": false, "endpoint_independent_nat": false,
"stack": "system", "stack": "system",
"include_uid": [ "include_uid": [
@@ -39,8 +46,8 @@
"exclude_package": [ "exclude_package": [
"com.android.captiveportallogin" "com.android.captiveportallogin"
], ],
...
... // 监听字段 // 监听字段
} }
``` ```
@@ -86,13 +93,31 @@ tun 接口的 IPv6 前缀。
#### strict_route #### strict_route
在 Linux 中启用 `auto_route` 时执行严格的路由规则。 启用 `auto_route` 时执行严格的路由规则。
*在 Linux 中*:
* 让不支持的网络无法到达 * 让不支持的网络无法到达
* 将所有连接路由到 tun * 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。 它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
*在 Windows 中*:
* 添加防火墙规则以阻止 Windows
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
造成的 DNS 泄露
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### inet4_route_address
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### inet6_route_address
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### endpoint_independent_nat #### endpoint_independent_nat
启用独立于端点的 NAT。 启用独立于端点的 NAT。
@@ -160,4 +185,4 @@ TCP/IP 栈。
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,8 @@
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=", "pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
"reserved": [0, 0, 0],
"workers": 4,
"mtu": 1408, "mtu": 1408,
"network": "tcp", "network": "tcp",
@@ -83,9 +85,21 @@ WireGuard peer public key.
WireGuard pre-shared key. WireGuard pre-shared key.
#### reserved
WireGuard reserved field bytes.
#### workers
WireGuard worker count.
CPU count is used by default.
#### mtu #### mtu
WireGuard MTU. 1408 will be used if empty. WireGuard MTU.
1408 will be used if empty.
#### network #### network

View File

@@ -15,6 +15,8 @@
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=", "private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=", "peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=", "pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
"reserved": [0, 0, 0],
"workers": 4,
"mtu": 1408, "mtu": 1408,
"network": "tcp", "network": "tcp",
@@ -85,9 +87,21 @@ WireGuard 对等公钥。
WireGuard 预共享密钥。 WireGuard 预共享密钥。
#### reserved
WireGuard 保留字段字节。
#### workers
WireGuard worker 数量。
默认使用 CPU 数量。
#### mtu #### mtu
WireGuard MTU。 默认1408。 WireGuard MTU。
默认使用 1408。
#### network #### network

View File

@@ -4,7 +4,8 @@
{ {
"detour": "upstream-out", "detour": "upstream-out",
"bind_interface": "en0", "bind_interface": "en0",
"bind_address": "0.0.0.0", "inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "5s", "connect_timeout": "5s",
@@ -17,9 +18,9 @@
### Fields ### Fields
| Field | Available Context | | Field | Available Context |
|---------------------------------------------------------------------------------------------------------------------|-------------------| |----------------------------------------------------------------------------------------------------------------------|-------------------|
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set | | `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set |
#### detour #### detour
@@ -29,9 +30,13 @@ The tag of the upstream outbound.
The network interface to bind to. The network interface to bind to.
#### bind_address #### inet4_bind_address
The address to bind to. The IPv4 address to bind to.
#### inet6_bind_address
The IPv6 address to bind to.
#### routing_mark #### routing_mark

View File

@@ -4,7 +4,8 @@
{ {
"detour": "upstream-out", "detour": "upstream-out",
"bind_interface": "en0", "bind_interface": "en0",
"bind_address": "0.0.0.0", "inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "5s", "connect_timeout": "5s",
@@ -17,9 +18,9 @@
### 字段 ### 字段
| 字段 | 可用上下文 | | 字段 | 可用上下文 |
|---------------------------------------------------------------------------------------------------------------------|--------------| |----------------------------------------------------------------------------------------------------------------------|--------------|
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 | | `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
#### detour #### detour
@@ -32,9 +33,13 @@
要绑定到的网络接口。 要绑定到的网络接口。
#### bind_address #### inet4_bind_address
要绑定的地址。 要绑定的 IPv4 地址。
#### inet6_bind_address
要绑定的 IPv6 地址。
#### routing_mark #### routing_mark

View File

@@ -8,6 +8,7 @@
"udp_fragment": false, "udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
"proxy_protocol": false, "proxy_protocol": false,
@@ -57,6 +58,12 @@ Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work. If the domain name is invalid (like tor), this will not work.
#### sniff_timeout
Timeout for sniffing.
300ms is used by default.
#### domain_strategy #### domain_strategy
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.

View File

@@ -8,6 +8,7 @@
"udp_fragment": false, "udp_fragment": false,
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_timeout": 300,
"proxy_protocol": false, "proxy_protocol": false,
@@ -58,6 +59,12 @@
如果域名无效(如 Tor将不生效。 如果域名无效(如 Tor将不生效。
#### sniff_timeout
探测超时时间。
默认使用 300ms。
#### domain_strategy #### domain_strategy
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only` 可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`

View File

@@ -192,10 +192,15 @@ Available fingerprint values:
* chrome * chrome
* firefox * firefox
* edge
* safari
* 360
* qq
* ios * ios
* android * android
* random * random
Chrome fingerprint will be used if empty.
### ACME Fields ### ACME Fields

View File

@@ -192,10 +192,16 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
* chrome * chrome
* firefox * firefox
* edge
* safari
* 360
* qq
* ios * ios
* android * android
* random * random
默认使用 chrome 指纹。
### ACME 字段 ### ACME 字段
!!! warning "" !!! warning ""

View File

@@ -0,0 +1,52 @@
```json
{
"dns": {
"rules": [
{
"domain": [
"clash.razord.top",
"yacd.haishan.me"
],
"server": "local"
},
{
"clash_mode": "direct",
"server": "local"
}
]
},
"outbounds": [
{
"type": "selector",
"tag": "default",
"outbounds": [
"proxy-a",
"proxy-b"
]
}
],
"route": {
"rules": [
{
"clash_mode": "direct",
"outbound": "direct"
},
{
"domain": [
"clash.razord.top",
"yacd.haishan.me"
],
"outbound": "direct"
}
],
"final": "default"
},
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"store_selected": true
}
}
}
```

View File

@@ -3,7 +3,8 @@
Configuration examples for sing-box. Configuration examples for sing-box.
* [Linux Server Installation](./linux-server-installation) * [Linux Server Installation](./linux-server-installation)
* [Shadowsocks Server](./ss-server) * [Tun](./tun)
* [Shadowsocks Client](./ss-client) * [DNS Hijack](./dns-hijack.md)
* [Shadowsocks Tun](./ss-tun) * [Shadowsocks](./shadowsocks)
* [DNS Hijack](./dns-hijack.md) * [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)

View File

@@ -3,7 +3,8 @@
sing-box 的配置示例。 sing-box 的配置示例。
* [Linux 服务器安装](./linux-server-installation) * [Linux 服务器安装](./linux-server-installation)
* [Shadowsocks 服务器](./ss-server) * [Tun](./tun)
* [Shadowsocks 客户端](./ss-client) * [DNS 劫持](./dns-hijack.md)
* [Shadowsocks Tun](./ss-tun) * [Shadowsocks](./shadowsocks)
* [DNS 劫持](./dns-hijack.md) * [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)

View File

@@ -0,0 +1,157 @@
# Shadowsocks
## Single User
#### Server
```json
{
"inbounds": [
{
"type": "shadowsocks",
"listen": "::",
"listen_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
]
}
```
#### Client
```json
{
"inbounds": [
{
"type": "mixed",
"listen": "::",
"listen_port": 2080
}
],
"outbounds": [
{
"type": "shadowsocks",
"server": "127.0.0.1",
"server_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
]
}
```
## Multiple Users
#### Server
```json
{
"inbounds": [
{
"type": "shadowsocks",
"listen": "::",
"listen_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"users": [
{
"name": "sekai",
"password": "BXYxVUXJ9NgF7c7KPLQjkg=="
}
]
}
]
}
```
#### Client
```json
{
"inbounds": [
{
"type": "mixed",
"listen": "::",
"listen_port": 2080
}
],
"outbounds": [
{
"type": "shadowsocks",
"server": "127.0.0.1",
"server_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==:BXYxVUXJ9NgF7c7KPLQjkg=="
}
]
}
```
## Relay
#### Server
```json
{
"inbounds": [
{
"type": "shadowsocks",
"listen": "::",
"listen_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
]
}
```
#### Relay
```json
{
"inbounds": [
{
"type": "shadowsocks",
"listen": "::",
"listen_port": 8081,
"method": "2022-blake3-aes-128-gcm",
"password": "BXYxVUXJ9NgF7c7KPLQjkg==",
"destinations": [
{
"name": "my_server",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"server": "127.0.0.1",
"server_port": 8080
}
]
}
]
}
```
#### Client
```json
{
"inbounds": [
{
"type": "mixed",
"listen": "::",
"listen_port": 2080
}
],
"outbounds": [
{
"type": "shadowsocks",
"server": "127.0.0.1",
"server_port": 8081,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==:BXYxVUXJ9NgF7c7KPLQjkg=="
}
]
}
```

View File

@@ -7,6 +7,8 @@
"type": "shadowtls", "type": "shadowtls",
"listen": "::", "listen": "::",
"listen_port": 4443, "listen_port": 4443,
"version": 2,
"password": "fuck me till the daylight",
"handshake": { "handshake": {
"server": "google.com", "server": "google.com",
"server_port": 443 "server_port": 443
@@ -45,6 +47,8 @@
"tag": "shadowtls-out", "tag": "shadowtls-out",
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 4443, "server_port": 4443,
"version": 2,
"password": "fuck me till the daylight",
"tls": { "tls": {
"enabled": true, "enabled": true,
"server_name": "google.com" "server_name": "google.com"

View File

@@ -1,21 +0,0 @@
```json
{
"inbounds": [
{
"type": "mixed",
"listen": "::",
"listen_port": 2080
}
],
"outbounds": [
{
"type": "shadowsocks",
"server": "::",
"server_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
]
}
```

View File

@@ -1,13 +0,0 @@
```json
{
"inbounds": [
{
"type": "shadowsocks",
"listen": "::",
"listen_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
]
}
```

View File

@@ -10,9 +10,18 @@
"tag": "local", "tag": "local",
"address": "223.5.5.5", "address": "223.5.5.5",
"detour": "direct" "detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
} }
], ],
"rules": [ "rules": [
{
"geosite": "category-ads-all",
"server": "block",
"disable_cache": true
},
{ {
"domain": "mydomain.com", "domain": "mydomain.com",
"geosite": "cn", "geosite": "cn",
@@ -26,6 +35,7 @@
"type": "tun", "type": "tun",
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"auto_route": true, "auto_route": true,
"strict_route": false,
"sniff": true "sniff": true
} }
], ],
@@ -58,13 +68,16 @@
"outbound": "dns-out" "outbound": "dns-out"
}, },
{ {
"geosite": "category-ads-all", "geosite": "cn",
"outbound": "block" "geoip": [
"private",
"cn"
],
"outbound": "direct"
}, },
{ {
"geosite": "cn", "geosite": "category-ads-all",
"geoip": "cn", "outbound": "block"
"outbound": "direct"
} }
], ],
"auto_detect_interface": true "auto_detect_interface": true

View File

@@ -7,11 +7,11 @@ the public internet.
##### on Android ##### on Android
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` is enabled. `auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
##### on Linux ##### on Linux
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled, you can switch to NetworkManager. `auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
#### System proxy #### System proxy

View File

@@ -6,11 +6,11 @@
##### Android ##### Android
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启. `auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启`strict_route` 禁用。
##### Linux ##### Linux
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启, 您可以切换到 NetworkManager. `auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启`strict_route` 禁用。
#### 系统代理 #### 系统代理

View File

@@ -95,7 +95,9 @@
| cn | 17.8M | 140.3M | | cn | 17.8M | 140.3M |
| cn (Loyalsoldier) | 74.3M | 246.7M | | cn (Loyalsoldier) | 74.3M | 246.7M |
#### Shadowsocks benchmark #### Benchmark
##### Shadowsocks
| / | none | aes-128-gcm | 2022-blake3-aes-128-gcm | | / | none | aes-128-gcm | 2022-blake3-aes-128-gcm |
|------------------------------------|:---------:|:-----------:|:-----------------------:| |------------------------------------|:---------:|:-----------:|:-----------------------:|
@@ -103,6 +105,13 @@
| shadowsocks-rust (v1.15.0-alpha.5) | 10.7 Gbps | / | 9.36 Gbps | | shadowsocks-rust (v1.15.0-alpha.5) | 10.7 Gbps | / | 9.36 Gbps |
| sing-box | 29.0 Gbps | / | 11.8 Gbps | | sing-box | 29.0 Gbps | / | 11.8 Gbps |
##### VMess
| / | TCP | HTTP | H2 TLS | WebSocket TLS | gRPC TLS |
|--------------------|:---------:|:---------:|:---------:|:-------------:|:---------:|
| v2ray-core (5.1.0) | 7.86 GBps | 2.86 Gbps | 1.83 Gbps | 2.36 Gbps | 2.43 Gbps |
| sing-box | 7.96 Gbps | 8.09 Gbps | 6.11 Gbps | 8.02 Gbps | 6.35 Gbps |
#### License #### License
| / | License | | / | License |

View File

@@ -24,14 +24,16 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
| Build Tag | Description | | Build Tag | Description |
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria), [Hysteria Outbound](./configuration/outbound/hysteria) and [V2Ray Transport#QUIC](./configuration/shared/v2ray-transport#quic). | | `with_quic` | Build with QUIC support, see [QUIC and HTTP3 DNS transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria), [Hysteria Outbound](./configuration/outbound/hysteria) and [V2Ray Transport#QUIC](./configuration/shared/v2ray-transport#quic). |
| `with_grpc` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). | | `with_grpc` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). |
| `with_dhcp` | Build with DHCP support, see [DHCP DNS transport](./configuration/dns/server). |
| `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). | | `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). |
| `with_shadowsocksr` | Build with ShadowsocksR support, see [ShadowsocksR outbound](./configuration/outbound/shadowsocksr). | | `with_shadowsocksr` | Build with ShadowsocksR support, see [ShadowsocksR outbound](./configuration/outbound/shadowsocksr). |
| `with_ech` | Build with TLS ECH extension support for TLS outbound, see [TLS](./configuration/shared/tls#ech). | | `with_ech` | Build with TLS ECH extension support for TLS outbound, see [TLS](./configuration/shared/tls#ech). |
| `with_utls` | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](./configuration/shared/tls#utls). | | `with_utls` | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](./configuration/shared/tls#utls). |
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). | | `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
| `with_clash_api` | Build with Clash API support, see [Experimental](./configuration/experimental#clash-api-fields). | | `with_clash_api` | Build with Clash API support, see [Experimental](./configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | Build with V2Ray API support, see [Experimental](./configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | Build with gVisor support, see [Tun inbound](./configuration/inbound/tun#stack) and [WireGuard outbound](./configuration/outbound/wireguard#system_interface). | | `with_gvisor` | Build with gVisor support, see [Tun inbound](./configuration/inbound/tun#stack) and [WireGuard outbound](./configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | Build with embedded Tor support, see [Tor outbound](./configuration/outbound/tor). | | `with_embedded_tor` (CGO required) | Build with embedded Tor support, see [Tor outbound](./configuration/outbound/tor). |
| `with_lwip` (CGO required) | Build with LWIP Tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). | | `with_lwip` (CGO required) | Build with LWIP Tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
@@ -45,10 +47,6 @@ sing-box version
It is also recommended to use systemd to manage sing-box service, It is also recommended to use systemd to manage sing-box service,
see [Linux server installation example](./examples/linux-server-installation). see [Linux server installation example](./examples/linux-server-installation).
## Contributors
[![](https://opencollective.com/sagernet/contributors.svg?width=740&button=false)](https://github.com/sagernet/sing-box/graphs/contributors)
## License ## License
``` ```

View File

@@ -25,13 +25,15 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
| 构建标志 | 描述 | | 构建标志 | 描述 |
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server)[Naive 入站](./configuration/inbound/naive)[Hysteria 入站](./configuration/inbound/hysteria)[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 | | `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server)[Naive 入站](./configuration/inbound/naive)[Hysteria 入站](./configuration/inbound/hysteria)[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 |
| `with_grpc` | 启用标准 gRPCuTLS](https://github.com/refraction-networking/utls) 支持, 参阅 [TLS](./configuration/shared/tls#utls)。 | | `with_grpc` | 启用标准 gRPC 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 | | `with_dhcp` | 启用 DHCP 支持,参阅 [DHCP DNS 传输层](./configuration/dns/server)。 |
| `with_clash_api` | 启用 Clash api 支 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
| `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 | | `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 | | `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 | | `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
| `with_utls` | 启用 [持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 | | `with_utls` | 启用 uTLS 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
| `with_clash_api` | 启用 Clash API 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
| `with_v2ray_api` | 启用 V2Rat API 支持,参阅 [实验性](./configuration/experimental#v2ray-api-fields)。 |
| `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 | | `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 |
| `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 | | `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 |
| `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 | | `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 |
@@ -42,13 +44,9 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
sing-box version sing-box version
``` ```
同时推荐使用 Systemd 来管理 sing-box 服务器实例。 同时推荐使用 systemd 来管理 sing-box 服务器实例。
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。 参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
## 贡献者
[![](https://opencollective.com/sagernet/contributors.svg?width=740&button=false)](https://github.com/sagernet/sing-box/graphs/contributors)
## 授权 ## 授权
``` ```

View File

@@ -1,14 +1,24 @@
//go:build with_clash_api
package experimental package experimental
import ( import (
"os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/clashapi"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
) )
func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { type ClashServerConstructor = func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
return clashapi.NewServer(router, logFactory, options)
var clashServerConstructor ClashServerConstructor
func RegisterClashServerConstructor(constructor ClashServerConstructor) {
clashServerConstructor = constructor
}
func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
if clashServerConstructor == nil {
return nil, os.ErrInvalid
}
return clashServerConstructor(router, logFactory, options)
} }

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