Compare commits

..

222 Commits

Author SHA1 Message Date
世界
4db7eb9d9e documentation: Update changelog 2023-03-31 16:29:08 +08:00
世界
fd4efd6104 Fix dns transport read 2023-03-31 14:31:35 +08:00
世界
19a35ec6a4 Fix http2 transport close 2023-03-31 14:31:35 +08:00
世界
2012c0ca1e Update release scripts 2023-03-31 14:31:35 +08:00
世界
187421c754 Append time to session log 2023-03-31 14:31:35 +08:00
世界
b3fb86d415 Accept "any" outbound in dns rule 2023-03-31 14:31:35 +08:00
世界
88fafd4e30 Fix dns routing context 2023-03-31 09:14:04 +08:00
世界
8056932f9c Update documentation 2023-03-27 08:23:01 +08:00
世界
c8af003bfc Update dependencies 2023-03-27 08:22:56 +08:00
世界
4999441a85 Fix missing default host in v2ray http transport`s request 2023-03-27 08:20:59 +08:00
世界
09b001e795 Revert remove install shell 2023-03-27 08:20:55 +08:00
世界
3b3a251008 Update LICENSE 2023-03-27 08:20:51 +08:00
世界
2e4eb9aa39 Update dockerfile 2023-03-24 08:29:11 +08:00
世界
77fd284703 documentation: Update changelog 2023-03-24 08:04:36 +08:00
世界
0a4517f4b7 Update dependencies 2023-03-24 07:06:45 +08:00
世界
4395db3206 documentation: Update set_system_proxy usage 2023-03-23 21:27:50 +08:00
世界
dd5b0abc67 Fix slow open 2023-03-23 17:14:38 +08:00
世界
466800aa3a Fix wireguard mutex 2023-03-23 15:43:17 +08:00
世界
4328c535a9 Improve timeout canceler 2023-03-23 15:39:12 +08:00
世界
f9516709da Update documentation 2023-03-23 07:54:24 +08:00
世界
5dce722879 Update dependencies 2023-03-23 07:49:14 +08:00
世界
9324a39d4e Fix import format 2023-03-20 23:01:54 +08:00
世界
84904c5206 Create working directory if not exists 2023-03-20 19:33:00 +08:00
世界
fe4b429fc2 hysteria: Accept inbound configuration without users 2023-03-20 19:22:46 +08:00
世界
f680d0acaf Add with_reality_server to release build tags 2023-03-20 17:36:59 +08:00
世界
4baff5aeb1 documentation: Update changelog 2023-03-20 17:32:59 +08:00
世界
f25296fb23 Update dependencies 2023-03-20 17:27:48 +08:00
世界
e717852c73 Fix optional listen address 2023-03-19 20:46:22 +08:00
世界
13dc70f649 Fix make build 2023-03-19 16:57:07 +08:00
世界
46040a71c3 Fix vision padding overflow 2023-03-19 10:25:35 +08:00
世界
0558b3fc5c ntp: Add write_to_system service option 2023-03-18 23:11:40 +08:00
世界
99b2ab5526 Add command to fetch a URL 2023-03-18 21:02:29 +08:00
世界
e5f3bb6344 Add command to connect an address 2023-03-18 20:27:38 +08:00
世界
c7f89ad88e Add multiple configuration support 2023-03-18 20:27:38 +08:00
世界
e0d9f79445 Fix test 2023-03-18 17:02:55 +08:00
世界
b6dbb69fc4 Fix write nil in buffered vectorised writer 2023-03-18 16:32:28 +08:00
世界
b76fabee65 documentation: Fix broken link 2023-03-18 16:31:15 +08:00
世界
872bcfd1c0 readme: Add packaging status 2023-03-17 17:58:08 +08:00
世界
b033c13ca2 documentation: Update stable changelog 2023-03-17 17:58:08 +08:00
renovate[bot]
2db188f3a1 dependencies: Update actions/setup-go action to v4
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 17:58:08 +08:00
世界
11de271c8f Add experimental debug options 2023-03-17 17:58:08 +08:00
世界
40c800c57c documentation: Fix typo 2023-03-17 13:34:36 +08:00
世界
91b0540e95 documentation: Update UoT application support status 2023-03-17 13:28:05 +08:00
世界
ce6d186345 Update documentation 2023-03-17 13:07:22 +08:00
世界
32bc4450a7 Update dependencies 2023-03-17 12:59:12 +08:00
世界
43f31b40ba Update UoT protocol 2023-03-17 12:57:48 +08:00
世界
a3a5185b15 platform: Fix bytes format 2023-03-16 11:28:54 +08:00
世界
14a0f180c8 ios: Add with_quic tag in build 2023-03-16 11:28:54 +08:00
世界
cc9cb0b477 platform: Add oom killer 2023-03-16 11:28:54 +08:00
世界
2cb0e37f50 platform: Add low memory interface 2023-03-16 00:36:04 +08:00
世界
dbd5be55b0 tun: Create gVisor stack by default in Apple Network Extension 2023-03-15 21:50:18 +08:00
世界
f674b4fbd5 Fix build embed tor for mobile 2023-03-15 20:59:45 +08:00
世界
5a4e8fea81 Fix lint 2023-03-15 14:56:06 +08:00
世界
78e02b52ca Update UoT protocol 2023-03-15 14:52:32 +08:00
世界
ffdaae90d7 Update dependencies 2023-03-15 11:59:15 +08:00
世界
c77681ea17 Fix close platform tun 2023-03-13 19:47:00 +08:00
世界
d824390167 Fix cross make build 2023-03-13 19:46:20 +08:00
世界
70cf681ff2 Remove length limit on short_id for reality TLS config 2023-03-13 19:46:16 +08:00
wwqgtxx
b004b9ec81 Fix stack wireguard device returning non-nil interface containing nil pointer 2023-03-13 19:46:16 +08:00
世界
657b05fd96 Print command to shell error 2023-03-13 19:46:16 +08:00
世界
caad60da45 Apply --disable-color to global logger 2023-03-13 11:23:00 +08:00
世界
7d22cf9b45 Support $schema in configuration file 2023-03-13 10:58:29 +08:00
世界
5cb178ca93 Update documentation 2023-03-12 23:07:38 +08:00
世界
16788008b6 Update dependencies 2023-03-12 23:07:24 +08:00
世界
6ec7a33046 Fix make install 2023-03-11 19:24:19 +08:00
世界
6af9c2b3ca Add health check support for http-based v2ray transport 2023-03-11 15:49:02 +08:00
世界
bdc620dab1 Fix http server usage 2023-03-11 15:05:07 +08:00
世界
a88820af31 Fix missing default shadowtls version 2023-03-11 10:12:46 +08:00
世界
3688f2e114 Update documentation 2023-03-10 11:15:00 +08:00
世界
a183958d53 Update dependencies 2023-03-09 23:24:05 +08:00
世界
1c8a9e91b7 Generate version during compilation 2023-03-09 23:24:05 +08:00
世界
325f6c71ff Fix windows interface monitor 2023-03-09 16:00:57 +08:00
世界
6c6c0792ad Update reality and uTLS 2023-03-09 10:51:01 +08:00
世界
9264f2307c Fix link broken in installation documentation page 2023-03-08 15:56:41 +08:00
世界
87dd328700 Fix build check 2023-03-08 15:23:46 +08:00
世界
0cec92dd0f Update documentation 2023-03-08 14:59:09 +08:00
世界
e88afa9665 Fix vmess server buffer 2023-03-07 17:07:37 +08:00
世界
3883a81315 Check constant.Version before build release 2023-03-06 16:34:44 +08:00
Dmitry R
c919ad079a systemd: Add reload command 2023-03-06 16:32:54 +08:00
世界
83593aee70 Fix vless read cache 2023-03-06 11:19:38 +08:00
世界
ac7cc09694 Update documentation 2023-03-05 23:37:12 +08:00
世界
d032e3568b Update dependencies 2023-03-05 21:38:02 +08:00
世界
c24df037ac Add documentation for tun platform options 2023-03-05 15:19:13 +08:00
世界
a2d43b3746 Fix open cache file 2023-03-05 14:57:50 +08:00
世界
5b3b74bd0f Fix vision read 2023-03-05 14:57:50 +08:00
世界
d24d3b26dc Fix uTLS randomized fingerprint 2023-03-05 14:57:50 +08:00
seiuneko
5db3cd7781 Fix documentation typo 2023-03-05 14:57:50 +08:00
世界
c88af8b081 Fix documentation 2023-03-05 14:57:50 +08:00
世界
45852ca3e7 Fix check config 2023-03-05 14:57:50 +08:00
Hellojack
03ce555104 Add generate commands 2023-03-05 11:21:32 +08:00
世界
dd0a07624e Add stop platform command 2023-03-04 00:40:47 +08:00
世界
b9b2b77814 Add reload platform command 2023-03-03 21:59:54 +08:00
世界
2366835121 Fix close conn 2023-03-03 19:27:30 +08:00
database64128
42e1dea7d2 Update .gitignore 2023-03-03 18:51:33 +08:00
Ella Hollywood
13d7716b02 Fix documentation typo 2023-03-03 16:35:06 +08:00
世界
7ecb9fc738 Minor fixes 2023-03-03 16:31:07 +08:00
世界
19b15e0d10 Fix UoT UDP address 2023-03-03 11:34:51 +08:00
database64128
0b15de461b Update tfo-go 2023-03-03 10:16:38 +08:00
世界
27aba99e6c Fix command client connect 2023-03-02 16:40:28 +08:00
世界
8151bcfd6b Add ios memory limit 2023-03-02 15:04:59 +08:00
世界
e8802357e1 Fix vless tests 2023-03-02 00:31:56 +08:00
世界
6e22c004f6 Improve server error handling 2023-03-02 00:18:35 +08:00
世界
20e1caa531 Fix custom tls server listener 2023-03-02 00:01:40 +08:00
世界
32ad3c3db3 Remove okhttp form modern fingerprint list 2023-03-01 21:17:30 +08:00
世界
1f5f8a7dde Fixed user flow in vless server 2023-03-01 20:28:40 +08:00
世界
6da1460795 Fix geo resource download path 2023-03-01 19:09:21 +08:00
世界
b14ae51f71 Fix create badhttp2 server 2023-03-01 19:09:21 +08:00
世界
5af8d001ae Refactor platform command api 2023-03-01 19:09:21 +08:00
世界
0ca344df5f Fix uTLS ALPN 2023-02-28 21:16:31 +08:00
世界
49f568abbd Separate uTLS random fingerprint 2023-02-28 21:10:11 +08:00
世界
3b4e811907 Add reality client fallback 2023-02-28 20:55:14 +08:00
世界
d0e9443031 Enable XUDP by default in VLESS 2023-02-28 20:52:26 +08:00
世界
f7e9d9ab1f Fix check early conn 2023-02-28 20:16:15 +08:00
世界
7834d6bca7 Add tun platform options 2023-02-28 19:02:27 +08:00
世界
ed50257735 Add custom TLS server support for http based v2ray transports 2023-02-28 13:03:44 +08:00
世界
f15f525c5c Merge tls interface to library 2023-02-28 11:30:46 +08:00
世界
e4bff0460d Update vision protocol 2023-02-27 15:07:15 +08:00
世界
5ce3ddee9b Add early conn interface 2023-02-26 23:08:20 +08:00
世界
22bf7a9509 Update reality server 2023-02-26 20:55:36 +08:00
世界
842730707c Update TUN creation 2023-02-26 20:55:15 +08:00
世界
a8f13bd956 Fix documentation 2023-02-25 17:25:56 +08:00
世界
cd5c2a7999 Update documentation 2023-02-25 16:28:39 +08:00
世界
fbc94b9e3e Add VLESS server, vision flow and reality TLS 2023-02-25 16:24:08 +08:00
zakuwaki
e766f25d55 Fix private ip will never be matched 2023-02-24 13:31:49 +08:00
世界
140ed9a4cb Fix platform wrapper 2023-02-24 13:00:49 +08:00
世界
60094884cd Update documentation 2023-02-22 11:45:31 +08:00
H3arn
0e8a4d141a Fix incorrect NTP server address 2023-02-21 23:08:05 +08:00
世界
17b78a6339 Fix documentation 2023-02-21 22:06:12 +08:00
世界
e99741159b Update documentation 2023-02-21 20:54:25 +08:00
世界
6b9603227b Add strict mode support for shadowtls v3 2023-02-21 20:51:26 +08:00
世界
23e8d282a3 Add multiple server names and multi-user support for shadowtls 2023-02-21 16:09:06 +08:00
世界
611d6bbfc5 Add NTP service 2023-02-21 16:09:06 +08:00
世界
f26785c0ba Add uTLS support for shadowtls v3 2023-02-20 21:04:07 +08:00
世界
5bcfb71737 Update dependencies 2023-02-20 21:04:07 +08:00
世界
4135c4974f Merge shadowtls to library 2023-02-20 13:53:06 +08:00
世界
222196b182 Add libbox wrapper 2023-02-20 11:07:49 +08:00
世界
86e55c5c1c Fix tproxy inbound 2023-02-19 18:56:38 +08:00
世界
73c068b96f Update documentation 2023-02-19 17:49:05 +08:00
世界
f516026540 Fix shadowtls in go versiojns below 1.20 2023-02-19 12:02:11 +08:00
dyhkwong
3c5bc842ed Update QUIC v2 version number and initial salt 2023-02-18 23:51:55 +08:00
世界
d8270a66f4 Update release script 2023-02-18 21:14:17 +08:00
世界
123c383eae Fix documentation 2023-02-18 19:33:35 +08:00
世界
67814faf92 Remove TLS min version for shadowtls v3 2023-02-18 19:26:05 +08:00
世界
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
309 changed files with 9996 additions and 2426 deletions

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:
push:
branches:
- main
- dev
- main-next
- dev-next
paths-ignore:
- '**.md'
@@ -12,8 +11,7 @@ on:
- '!.github/workflows/debug.yml'
pull_request:
branches:
- main
- dev
- main-next
- dev-next
jobs:
@@ -21,24 +19,20 @@ jobs:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
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')
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
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
@@ -60,22 +54,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: 1.18.7
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: |
go test -v ./...
run: make
cross:
strategy:
matrix:
@@ -192,19 +185,19 @@ jobs:
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
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')
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
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
@@ -213,7 +206,7 @@ jobs:
id: build
run: make
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sing-box-${{ matrix.name }}
path: sing-box*

View File

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

View File

@@ -3,45 +3,40 @@ name: Lint
on:
push:
branches:
- dev
- main-next
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
- '!.github/workflows/lint.yml'
pull_request:
branches:
- dev
- main-next
- dev-next
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
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')
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
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Get dependencies
run: |
go mod download -x
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:

View File

@@ -10,9 +10,11 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-static-i18n
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
- run: |
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
- run: |
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

View File

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

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

9
.gitignore vendored
View File

@@ -5,4 +5,11 @@
/site/
/bin/
/dist/
/sing-box
/sing-box
/sing-box.exe
/build/
/*.jar
/*.aar
/*.xcframework/
.DS_Store
/config.d/

View File

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

View File

@@ -1,6 +1,7 @@
project_name: sing-box
builds:
- main: ./cmd/sing-box
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
@@ -9,19 +10,17 @@ builds:
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_reality_server
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- android_arm64
- android_amd64
- android_amd64_v3
- linux_amd64_v1
- linux_amd64_v3
- linux_arm64
@@ -35,6 +34,54 @@ builds:
- darwin_amd64_v3
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_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:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives:

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>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
@@ -8,13 +8,13 @@ ENV CGO_ENABLED=0
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
-o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \

View File

@@ -11,4 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.

View File

@@ -1,9 +1,15 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release
@@ -11,16 +17,16 @@ build:
go build $(PARAMS) $(MAIN)
install:
go install $(PARAMS) $(MAIN)
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
fmt:
@gofumpt -l -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:
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:
GOOS=linux golangci-lint run ./...
@@ -42,14 +48,14 @@ proto_install:
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot:
goreleaser release --rm-dist --snapshot
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist
release:
goreleaser release --rm-dist --skip-publish
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
@@ -71,6 +77,14 @@ test_stdio:
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
lib:
go run ./cmd/internal/build_libbox
lib_install:
go get -v -d
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
clean:
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box

View File

@@ -2,6 +2,8 @@
The universal proxy platform.
[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions)
## Documentation
https://sing-box.sagernet.org
@@ -23,4 +25,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

View File

@@ -10,6 +10,7 @@ import (
type ClashServer interface {
Service
PreStarter
Mode() string
StoreSelected() bool
CacheFile() ClashCacheFile
@@ -45,6 +46,6 @@ type V2RayServer interface {
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn
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

@@ -46,6 +46,10 @@ type InboundContext struct {
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
// dns cache
QueryType uint16
}
type inboundContextKey struct{}

15
adapter/prestart.go Normal file
View File

@@ -0,0 +1,15 @@
package adapter
type PreStarter interface {
PreStart() error
}
func PreStart(starter any) error {
if preService, ok := starter.(PreStarter); ok {
err := preService.PreStart()
if err != nil {
return err
}
}
return nil
}

View File

@@ -34,12 +34,15 @@ type Router interface {
InterfaceFinder() control.InterfaceFinder
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
TimeService
ClashServer() ClashServer
SetClashServer(server ClashServer)
@@ -47,6 +50,20 @@ type Router interface {
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 {
Service
Type() string

8
adapter/time.go Normal file
View File

@@ -0,0 +1,8 @@
package adapter
import "time"
type TimeService interface {
Service
TimeFunc() func() time.Time
}

View File

@@ -3,6 +3,10 @@ package adapter
import (
"context"
"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 {
@@ -12,6 +16,12 @@ type V2RayServerTransport interface {
Close() error
}
type V2RayServerTransportHandler interface {
N.TCPConnectionHandler
E.Handler
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
}
type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error)
}

197
box.go
View File

@@ -9,7 +9,9 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -23,49 +25,57 @@ import (
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
done chan struct{}
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
preServices map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
func New(ctx context.Context, options option.Options) (*Box, error) {
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
createdAt := time.Now()
logOptions := common.PtrValueOrDefault(options.Log)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
var needV2RayAPI bool
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
}
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
needClashAPI = true
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
logOptions := common.PtrValueOrDefault(options.Log)
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
var logWriter io.Writer
switch logOptions.Output {
case "", "stderr":
case "":
if platformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
@@ -79,10 +89,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter)
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
@@ -97,11 +107,12 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router, err := route.NewRouter(
ctx,
logFactory.NewLogger("router"),
logFactory.NewLogger("dns"),
logFactory,
common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
platformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
@@ -121,6 +132,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
platformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -154,37 +166,57 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
if err != nil {
return nil, err
}
var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
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 {
return nil, E.Cause(err, "create clash api server")
}
router.SetClashServer(clashServer)
preServices["clash api"] = clashServer
}
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
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)
preServices["v2ray api"] = v2rayServer
}
return &Box{
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}),
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
preServices: preServices,
postServices: postServices,
done: make(chan struct{}),
}, nil
}
func (s *Box) PreStart() error {
err := s.preStart()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) Start() error {
err := s.start()
if err != nil {
@@ -198,11 +230,19 @@ func (s *Box) Start() error {
}
}()
s.Close()
return err
}
return err
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) start() error {
func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
err := adapter.PreStart(service)
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
}
}
for i, out := range s.outbounds {
if starter, isStarter := out.(common.Starter); isStarter {
err := starter.Start()
@@ -217,10 +257,20 @@ func (s *Box) start() error {
}
}
}
err := s.router.Start()
return s.router.Start()
}
func (s *Box) start() error {
err := s.preStart()
if err != nil {
return err
}
for serviceName, service := range s.preServices {
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for i, in := range s.inbounds {
err = in.Start()
if err != nil {
@@ -233,19 +283,12 @@ func (s *Box) start() error {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
if s.clashServer != nil {
err = s.clashServer.Start()
for serviceName, service := range s.postServices {
err = service.Start()
if err != nil {
return E.Cause(err, "start clash api server")
return E.Cause(err, "start ", serviceName)
}
}
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)")
return nil
}
@@ -256,19 +299,43 @@ func (s *Box) Close() error {
default:
close(s.done)
}
for _, in := range s.inbounds {
in.Close()
var errors error
for serviceName, service := range s.postServices {
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
}
for _, out := range s.outbounds {
common.Close(out)
for i, in := range s.inbounds {
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
}
return common.Close(
s.router,
s.logFactory,
s.clashServer,
s.v2rayServer,
common.PtrOrNil(s.logFile),
)
for i, out := range s.outbounds {
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
})
}
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
for serviceName, service := range s.preServices {
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
}
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
}
func (s *Box) Router() adapter.Router {

View File

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

View File

@@ -0,0 +1,136 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
_ "github.com/sagernet/gomobile/event/key"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw"
)
var (
debugEnabled bool
target string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
}
func main() {
flag.Parse()
build_shared.FindMobile()
switch target {
case "android":
buildAndroid()
case "ios":
buildiOS()
}
}
var (
sharedFlags []string
debugFlags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-ldflags")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
}
func buildAndroid() {
build_shared.FindSDK()
args := []string{
"bind",
"-v",
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
} else {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
const name = "libbox.aar"
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.FileExists(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(name, filepath.Join(copyPath, name))
if err != nil {
log.Fatal(err)
}
log.Info("copied to ", copyPath)
}
}
func buildiOS() {
args := []string{
"bind",
"-v",
"-target", "ios,iossimulator,macos",
"-libname=box",
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
} else {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
}
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sing-box-for-ios")
if rw.FileExists(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
os.RemoveAll(targetDir)
os.Rename("Libbox.xcframework", targetDir)
log.Info("copied to ", targetDir)
}
}

View File

@@ -0,0 +1,92 @@
package build_shared
import (
"go/build"
"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
}
var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if !rw.FileExists(goBin + "/" + "gobind") {
log.Fatal("missing gomobile installation")
}
GoBinPath = goBin
}

View File

@@ -0,0 +1,16 @@
package build_shared
import "github.com/sagernet/sing/common/shell"
func ReadTag() (string, error) {
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
if err != nil {
return currentTag, err
}
currentTagRev, _ := shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()
if currentTagRev == currentTag {
return currentTag[1:], nil
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
return currentTagRev[1:] + "-" + shortCommit, nil
}

View File

@@ -0,0 +1,21 @@
package main
import (
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
)
func main() {
currentTag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
_, err = os.Stdout.WriteString("unknown\n")
} else {
_, err = os.Stdout.WriteString(currentTag + "\n")
}
if err != nil {
log.Error(err)
}
}

View File

@@ -26,12 +26,15 @@ func init() {
}
func check() error {
options, err := readConfig()
options, err := readConfigAndMerge()
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
_, err = box.New(ctx, options)
instance, err := box.New(ctx, options, nil)
if err == nil {
instance.Close()
}
cancel()
return err
}

View File

@@ -33,6 +33,44 @@ func init() {
}
func format() error {
optionsList, err := readConfig()
if err != nil {
return err
}
for _, optionsEntry := range optionsList {
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(optionsEntry.options)
if err != nil {
return E.Cause(err, "encode config")
}
outputPath, _ := filepath.Abs(optionsEntry.path)
if !commandFormatFlagWrite {
if len(optionsList) > 1 {
os.Stdout.WriteString(outputPath + "\n")
}
os.Stdout.WriteString(buffer.String() + "\n")
continue
}
if bytes.Equal(optionsEntry.content, buffer.Bytes()) {
continue
}
output, err := os.Create(optionsEntry.path)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
os.Stderr.WriteString(outputPath + "\n")
}
return nil
}
func formatOne(configPath string) error {
configContent, err := os.ReadFile(configPath)
if err != nil {
return E.Cause(err, "read config")

View File

@@ -0,0 +1,139 @@
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"os"
"strconv"
"github.com/sagernet/sing-box/log"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var commandGenerate = &cobra.Command{
Use: "generate",
Short: "Generate things",
}
func init() {
commandGenerate.AddCommand(commandGenerateUUID)
commandGenerate.AddCommand(commandGenerateRandom)
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
mainCommand.AddCommand(commandGenerate)
}
var (
outputBase64 bool
outputHex bool
)
var commandGenerateRandom = &cobra.Command{
Use: "rand <length>",
Short: "Generate random bytes",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateRandom(args)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateRandom.Flags().BoolVar(&outputBase64, "base64", false, "Generate base64 string")
commandGenerateRandom.Flags().BoolVar(&outputHex, "hex", false, "Generate hex string")
}
func generateRandom(args []string) error {
length, err := strconv.Atoi(args[0])
if err != nil {
return err
}
randomBytes := make([]byte, length)
_, err = rand.Read(randomBytes)
if err != nil {
return err
}
if outputBase64 {
_, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + "\n")
} else if outputHex {
_, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + "\n")
} else {
_, err = os.Stdout.Write(randomBytes)
}
return err
}
var commandGenerateUUID = &cobra.Command{
Use: "uuid",
Short: "Generate UUID string",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateUUID()
if err != nil {
log.Fatal(err)
}
},
}
func generateUUID() error {
newUUID, err := uuid.NewV4()
if err != nil {
return err
}
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
return err
}
var commandGenerateWireGuardKeyPair = &cobra.Command{
Use: "wg-keypair",
Short: "Generate WireGuard key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateWireGuardKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateWireGuardKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
return nil
}
var commandGenerateRealityKeyPair = &cobra.Command{
Use: "reality-keypair",
Short: "Generate reality key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateRealityKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateRealityKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
return nil
}

View File

@@ -5,10 +5,14 @@ import (
"io"
"os"
"os/signal"
"path/filepath"
runtimeDebug "runtime/debug"
"sort"
"strings"
"syscall"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -31,29 +35,88 @@ func init() {
mainCommand.AddCommand(commandRun)
}
func readConfig() (option.Options, error) {
type OptionsEntry struct {
content []byte
path string
options option.Options
}
func readConfigAt(path string) (*OptionsEntry, error) {
var (
configContent []byte
err error
)
if configPath == "stdin" {
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(configPath)
configContent, err = os.ReadFile(path)
}
if err != nil {
return option.Options{}, E.Cause(err, "read config")
return nil, E.Cause(err, "read config at ", path)
}
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
return nil, E.Cause(err, "decode config at ", path)
}
return options, nil
return &OptionsEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readConfig() ([]*OptionsEntry, error) {
var optionsList []*OptionsEntry
for _, path := range configPaths {
optionsEntry, err := readConfigAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range configDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read config directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
}
sort.Slice(optionsList, func(i, j int) bool {
return optionsList[i].path < optionsList[j].path
})
return optionsList, nil
}
func readConfigAndMerge() (option.Options, error) {
optionsList, err := readConfig()
if err != nil {
return option.Options{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var mergedOptions option.Options
for _, options := range optionsList {
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}
}
return mergedOptions, nil
}
func create() (*box.Box, context.CancelFunc, error) {
options, err := readConfig()
options, err := readConfigAndMerge()
if err != nil {
return nil, nil, err
}
@@ -64,11 +127,25 @@ func create() (*box.Box, context.CancelFunc, error) {
options.Log.DisableColor = true
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options)
instance, err := box.New(ctx, options, nil)
if err != nil {
cancel()
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()
if err != nil {
cancel()
@@ -80,6 +157,7 @@ func create() (*box.Box, context.CancelFunc, error) {
func run() error {
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for {
instance, cancel, err := create()
if err != nil {

55
cmd/sing-box/cmd_tools.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"context"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra"
)
var commandToolsFlagOutbound string
var commandTools = &cobra.Command{
Use: "tools",
Short: "Experimental tools",
}
func init() {
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
mainCommand.AddCommand(commandTools)
}
func createPreStartedClient() (*box.Box, error) {
options, err := readConfigAndMerge()
if err != nil {
return nil, err
}
instance, err := box.New(context.Background(), options, nil)
if err != nil {
return nil, E.Cause(err, "create service")
}
err = instance.PreStart()
if err != nil {
return nil, E.Cause(err, "start service")
}
return instance, nil
}
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
if outboundTag == "" {
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
if outbound == nil {
return nil, E.New("missing default outbound")
}
return outbound, nil
} else {
outbound, loaded := instance.Router().Outbound(outboundTag)
if !loaded {
return nil, E.New("outbound not found: ", outboundTag)
}
return outbound, nil
}
}

View File

@@ -0,0 +1,73 @@
package main
import (
"context"
"os"
"github.com/sagernet/sing-box/log"
"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/sing/common/task"
"github.com/spf13/cobra"
)
var commandConnectFlagNetwork string
var commandConnect = &cobra.Command{
Use: "connect [address]",
Short: "Connect to an address",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := connect(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
commandTools.AddCommand(commandConnect)
}
func connect(address string) error {
switch N.NetworkName(commandConnectFlagNetwork) {
case N.NetworkTCP, N.NetworkUDP:
default:
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
}
instance, err := createPreStartedClient()
if err != nil {
return err
}
defer instance.Close()
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
if err != nil {
return err
}
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
if err != nil {
return E.Cause(err, "connect to server")
}
var group task.Group
group.Append("upload", func(ctx context.Context) error {
return common.Error(bufio.Copy(conn, os.Stdin))
})
group.Append("download", func(ctx context.Context) error {
return common.Error(bufio.Copy(os.Stdout, conn))
})
group.Cleanup(func() {
conn.Close()
})
err = group.Run(context.Background())
if E.IsClosed(err) {
log.Info(err)
} else {
log.Error(err)
}
return nil
}

View File

@@ -0,0 +1,91 @@
package main
import (
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"os"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
"github.com/spf13/cobra"
)
var commandFetch = &cobra.Command{
Use: "fetch",
Short: "Fetch an URL",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := fetch(args)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandTools.AddCommand(commandFetch)
}
var httpClient *http.Client
func fetch(args []string) error {
instance, err := createPreStartedClient()
if err != nil {
return err
}
defer instance.Close()
httpClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
ForceAttemptHTTP2: true,
},
}
defer httpClient.CloseIdleConnections()
for _, urlString := range args {
parsedURL, err := url.Parse(urlString)
if err != nil {
return err
}
switch parsedURL.Scheme {
case "":
parsedURL.Scheme = "http"
fallthrough
case "http", "https":
err = fetchHTTP(parsedURL)
if err != nil {
return err
}
}
}
return nil
}
func fetchHTTP(parsedURL *url.URL) error {
request, err := http.NewRequest("GET", parsedURL.String(), nil)
if err != nil {
return err
}
request.Header.Add("User-Agent", "curl/7.88.0")
response, err := httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
_, err = bufio.Copy(os.Stdout, response.Body)
if errors.Is(err, io.EOF) {
return nil
}
return err
}

View File

@@ -0,0 +1,69 @@
package main
import (
"context"
"os"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/spf13/cobra"
)
var (
commandSyncTimeFlagServer string
commandSyncTimeOutputFormat string
commandSyncTimeWrite bool
)
var commandSyncTime = &cobra.Command{
Use: "synctime",
Short: "Sync time using the NTP protocol",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := syncTime()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
commandTools.AddCommand(commandSyncTime)
}
func syncTime() error {
instance, err := createPreStartedClient()
if err != nil {
return err
}
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
if err != nil {
return err
}
defer instance.Close()
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
if serverAddress.Port == 0 {
serverAddress.Port = 123
}
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
if err != nil {
return err
}
if commandSyncTimeWrite {
err = settings.SetSystemTime(response.Time)
if err != nil {
return E.Cause(err, "write time to system")
}
}
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
return nil
}

View File

@@ -25,9 +25,9 @@ func init() {
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.Bytes(memStats.HeapInuse))
memObject.Put("stack", humanize.Bytes(memStats.StackInuse))
memObject.Put("idle", humanize.Bytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -2,6 +2,7 @@ package main
import (
"os"
"time"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
@@ -10,9 +11,10 @@ import (
)
var (
configPath string
workingDir string
disableColor bool
configPaths []string
configDirectories []string
workingDir string
disableColor bool
)
var mainCommand = &cobra.Command{
@@ -21,7 +23,8 @@ var mainCommand = &cobra.Command{
}
func init() {
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
}
@@ -33,9 +36,19 @@ func main() {
}
func preRun(cmd *cobra.Command, args []string) {
if disableColor {
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
}
if workingDir != "" {
_, err := os.Stat(workingDir)
if err != nil {
os.MkdirAll(workingDir, 0o777)
}
if err := os.Chdir(workingDir); err != nil {
log.Fatal(err)
}
}
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
}

View File

@@ -0,0 +1,80 @@
package badjsonmerge
import (
"encoding/json"
"reflect"
"github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
rawSource, err := json.Marshal(source)
if err != nil {
return option.Options{}, E.Cause(err, "marshal source")
}
rawDestination, err := json.Marshal(destination)
if err != nil {
return option.Options{}, E.Cause(err, "marshal destination")
}
rawMerged, err := MergeJSON(rawSource, rawDestination)
if err != nil {
return option.Options{}, E.Cause(err, "merge options")
}
var merged option.Options
err = json.Unmarshal(rawMerged, &merged)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged options")
}
return merged, nil
}
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
source, err := badjson.Decode(rawSource)
if err != nil {
return nil, E.Cause(err, "decode source")
}
destination, err := badjson.Decode(rawDestination)
if err != nil {
return nil, E.Cause(err, "decode destination")
}
merged, err := mergeJSON(source, destination)
if err != nil {
return nil, err
}
return json.Marshal(merged)
}
func mergeJSON(anySource any, anyDestination any) (any, error) {
switch destination := anyDestination.(type) {
case badjson.JSONArray:
switch source := anySource.(type) {
case badjson.JSONArray:
destination = append(destination, source...)
default:
destination = append(destination, source)
}
return destination, nil
case *badjson.JSONObject:
switch source := anySource.(type) {
case *badjson.JSONObject:
for _, entry := range source.Entries() {
oldValue, loaded := destination.Get(entry.Key)
if loaded {
var err error
entry.Value, err = mergeJSON(entry.Value, oldValue)
if err != nil {
return nil, E.Cause(err, "merge object item ", entry.Key)
}
}
destination.Put(entry.Key, entry.Value)
}
default:
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
}
return destination, nil
default:
return destination, nil
}
}

View File

@@ -0,0 +1,59 @@
package badjsonmerge
import (
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network"
"github.com/stretchr/testify/require"
)
func TestMergeJSON(t *testing.T) {
t.Parallel()
options := option.Options{
Log: &option.LogOptions{
Level: "info",
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: N.NetworkTCP,
Outbound: "direct",
},
},
},
},
}
anotherOptions := option.Options{
Outbounds: []option.Outbound{
{
Type: C.TypeDirect,
Tag: "direct",
},
},
}
thirdOptions := option.Options{
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: N.NetworkUDP,
Outbound: "direct",
},
},
},
},
}
mergeOptions, err := MergeOptions(options, anotherOptions)
require.NoError(t, err)
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
require.NoError(t, err)
require.Equal(t, "info", mergeOptions.Log.Level)
require.Equal(t, 2, len(mergeOptions.Route.Rules))
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
}

View File

@@ -1,48 +0,0 @@
package canceler
import (
"context"
"time"
)
type Instance struct {
ctx context.Context
cancelFunc context.CancelFunc
timer *time.Timer
timeout time.Duration
}
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
instance := &Instance{
ctx,
cancelFunc,
time.NewTimer(timeout),
timeout,
}
go instance.wait()
return instance
}
func (i *Instance) Update() bool {
if !i.timer.Stop() {
return false
}
if !i.timer.Reset(i.timeout) {
return false
}
return true
}
func (i *Instance) wait() {
select {
case <-i.timer.C:
case <-i.ctx.Done():
}
i.Close()
}
func (i *Instance) Close() error {
i.timer.Stop()
i.cancelFunc()
return nil
}

View File

@@ -1,49 +0,0 @@
package canceler
import (
"context"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type PacketConn struct {
N.PacketConn
instance *Instance
}
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
ctx, cancel := context.WithCancel(ctx)
instance := New(ctx, cancel, timeout)
return ctx, &PacketConn{conn, instance}
}
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if err == nil {
c.instance.Update()
}
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
err := c.PacketConn.WritePacket(buffer, destination)
if err == nil {
c.instance.Update()
}
return err
}
func (c *PacketConn) Close() error {
return common.Close(
c.PacketConn,
c.instance,
)
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}

View File

@@ -0,0 +1,54 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/x/list"
)
type Conn struct {
net.Conn
element *list.Element[io.Closer]
}
func NewConn(conn net.Conn) (*Conn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := killerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &Conn{
Conn: conn,
element: element,
}, nil
}
func (c *Conn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.Conn.Close()
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}

View File

@@ -0,0 +1,38 @@
package conntrack
import (
"runtime"
runtimeDebug "runtime/debug"
"time"
E "github.com/sagernet/sing/common/exceptions"
)
var (
KillerEnabled bool
MemoryLimit int64
killerLastCheck time.Time
)
func killerCheck() error {
if !KillerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(killerLastCheck) < 3*time.Second {
return nil
}
killerLastCheck = nowTime
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
if inuseMemory > MemoryLimit {
Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}

View File

@@ -0,0 +1,54 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/x/list"
)
type PacketConn struct {
net.PacketConn
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := killerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &PacketConn{
PacketConn: conn,
element: element,
}, nil
}
func (c *PacketConn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.PacketConn.Close()
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

View File

@@ -0,0 +1,38 @@
package conntrack
import (
"io"
"sync"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/x/list"
)
var (
connAccess sync.RWMutex
openConnection list.List[io.Closer]
)
func Count() int {
return openConnection.Len()
}
func List() []io.Closer {
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]io.Closer, 0, openConnection.Len())
for element := openConnection.Front(); element != nil; element = element.Next() {
connList = append(connList, element.Value)
}
return connList
}
func Close() {
connAccess.Lock()
defer connAccess.Unlock()
for element := openConnection.Front(); element != nil; element = element.Next() {
common.Close(element.Value)
element.Value = nil
}
openConnection.Init()
}

View File

@@ -0,0 +1,5 @@
//go:build !with_conntrack
package conntrack
const Enabled = false

View File

@@ -0,0 +1,5 @@
//go:build with_conntrack
package conntrack
const Enabled = true

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/common/warning"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -13,8 +14,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/database64128/tfo-go/v2"
"github.com/sagernet/tfo-go"
)
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
@@ -71,15 +71,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() {
const useInterfaceName = C.IsLinux
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux {
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
})
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.DefaultInterface() != "" {
@@ -168,16 +160,30 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
}
}
if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address)
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return DialSlowContext(&d.dialer6, ctx, network, address)
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsIPv6() {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
} else {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
}
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewConn(conn)
}
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewPacketConn(conn)
}

View File

@@ -12,8 +12,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/database64128/tfo-go/v2"
"github.com/sagernet/tfo-go"
)
type slowOpenConn struct {
@@ -120,6 +119,10 @@ func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil
}
func (c *slowOpenConn) NeedHandshake() 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)

View File

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

View File

@@ -24,13 +24,13 @@ func (l *Listener) Accept() (net.Conn, error) {
bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader)
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
return nil, err
return nil, &Error{err}
}
if bufReader.Buffered() > 0 {
cache := buf.NewSize(bufReader.Buffered())
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
if err != nil {
return nil, err
return nil, &Error{err}
}
conn = bufio.NewCachedConn(conn, cache)
}
@@ -42,3 +42,21 @@ func (l *Listener) Accept() (net.Conn, error) {
}
return conn, nil
}
var _ net.Error = (*Error)(nil)
type Error struct {
error
}
func (e *Error) Unwrap() error {
return e.error
}
func (e *Error) Timeout() bool {
return false
}
func (e *Error) Temporary() bool {
return true
}

View File

@@ -1,6 +1,7 @@
package redir
import (
"encoding/binary"
"net"
"net/netip"
"os"
@@ -29,7 +30,9 @@ func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err erro
if err != nil {
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
})

View File

@@ -6,8 +6,8 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -26,9 +26,9 @@ func init() {
func runAndroidShell(name string, args ...string) error {
if !useRish {
return common.Exec(name, args...).Attach().Run()
return shell.Exec(name, args...).Attach().Run()
} else {
return common.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
}
}

View File

@@ -6,9 +6,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list"
)
@@ -34,13 +34,13 @@ func (p *systemProxy) update(event int) error {
return err
}
if p.isMixed {
err = common.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return err
}
@@ -51,19 +51,19 @@ func (p *systemProxy) unset() error {
return err
}
if p.isMixed {
err = common.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = common.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
}
return err
}
func getInterfaceDisplayName(name string) (string, error) {
content, err := common.Exec("networksetup", "-listallhardwareports").Read()
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
if err != nil {
return "", err
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -27,9 +28,9 @@ func init() {
func runAsUser(name string, args ...string) error {
if os.Getuid() != 0 {
return common.Exec(name, args...).Attach().Run()
return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" {
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}

View File

@@ -7,7 +7,7 @@ import (
)
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 {
return nil, err
}

View File

@@ -0,0 +1,12 @@
//go:build !(windows || linux || darwin)
package settings
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}

View File

@@ -0,0 +1,14 @@
//go:build linux || darwin
package settings
import (
"time"
"golang.org/x/sys/unix"
)
func SetSystemTime(nowTime time.Time) error {
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
return unix.Settimeofday(&timeVal)
}

View File

@@ -0,0 +1,32 @@
package settings
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
)
func SetSystemTime(nowTime time.Time) error {
var systemTime windows.Systemtime
systemTime.Year = uint16(nowTime.Year())
systemTime.Month = uint16(nowTime.Month())
systemTime.Day = uint16(nowTime.Day())
systemTime.Hour = uint16(nowTime.Hour())
systemTime.Minute = uint16(nowTime.Minute())
systemTime.Second = uint16(nowTime.Second())
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
proc := dllKernel32.NewProc("SetSystemTime")
_, _, err := proc.Call(
uintptr(unsafe.Pointer(&systemTime)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return err
}
return nil
}

View File

@@ -13,13 +13,13 @@ import (
const (
VersionDraft29 = 0xff00001d
Version1 = 0x1
Version2 = 0x709a50c4
Version2 = 0x6b3343cf
)
var (
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
SaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
)
const (

View File

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

View File

@@ -2,16 +2,15 @@ package tls
import (
"context"
"crypto/tls"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
@@ -31,29 +30,19 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(router, serverAddress, options)
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityClient(router, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(router, serverAddress, options)
} else {
return NewSTDClient(serverAddress, options)
return NewSTDClient(router, serverAddress, options)
}
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
tlsConn := config.Client(conn)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err := tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
var badConn badtls.TLSConn
badConn, err = badtls.Create(stdConn)
if err == nil {
return badConn, nil
}
}
return tlsConn, nil
return aTLS.ClientHandshake(ctx, conn, config)
}
type Dialer struct {

View File

@@ -1,41 +1,25 @@
package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
aTLS "github.com/sagernet/sing/common/tls"
)
type (
STDConfig = tls.Config
STDConn = tls.Conn
Config = aTLS.Config
ConfigCompat = aTLS.ConfigCompat
ServerConfig = aTLS.ServerConfig
ServerConfigCompat = aTLS.ServerConfigCompat
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
Conn = aTLS.Conn
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
)
type Config interface {
ServerName() string
SetServerName(serverName string)
NextProtos() []string
SetNextProtos(nextProto []string)
Config() (*STDConfig, error)
Client(conn net.Conn) Conn
Clone() Config
}
type ServerConfig interface {
Config
adapter.Service
Server(conn net.Conn) Conn
}
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
}
func ParseTLSVersion(version string) (uint16, error) {
switch version {
case "1.0":

View File

@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for ECH")
}
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
return &echConnWrapper{cftls.Client(conn, e.config)}
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
}
func (e *ECHClientConfig) Clone() Config {
@@ -76,6 +76,10 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
}
}
func (c *echConnWrapper) Upstream() any {
return c.Conn
}
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
@@ -90,6 +94,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
}
var tlsConfig cftls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -11,7 +11,10 @@ import (
"time"
)
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
if timeFunc == nil {
timeFunc = time.Now
}
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
@@ -22,8 +25,8 @@ func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: time.Now().Add(time.Hour * -1),
NotAfter: time.Now().Add(time.Hour),
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: timeFunc().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,

View File

@@ -0,0 +1,231 @@
//go:build with_utls
package tls
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
mRand "math/rand"
"net"
"net/http"
"reflect"
"strings"
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
aTLS "github.com/sagernet/sing/common/tls"
utls "github.com/sagernet/utls"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
var _ ConfigCompat = (*RealityClientConfig)(nil)
type RealityClientConfig struct {
uClient *UTLSClientConfig
publicKey []byte
shortID [8]byte
}
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
if options.UTLS == nil || !options.UTLS.Enabled {
return nil, E.New("uTLS is required by reality client")
}
uClient, err := NewUTLSClient(router, serverAddress, options)
if err != nil {
return nil, err
}
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public_key")
}
if len(publicKey) != 32 {
return nil, E.New("invalid public_key")
}
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(options.Reality.ShortID))
if err != nil {
return nil, E.Cause(err, "decode short_id")
}
if decodedLen > 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{uClient, publicKey, shortID}, nil
}
func (e *RealityClientConfig) ServerName() string {
return e.uClient.ServerName()
}
func (e *RealityClientConfig) SetServerName(serverName string) {
e.uClient.SetServerName(serverName)
}
func (e *RealityClientConfig) NextProtos() []string {
return e.uClient.NextProtos()
}
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
e.uClient.SetNextProtos(nextProto)
}
func (e *RealityClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for reality")
}
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
return ClientHandshake(context.Background(), conn, e)
}
func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
verifier := &realityVerifier{
serverName: e.uClient.ServerName(),
}
uConfig := e.uClient.config.Clone()
uConfig.InsecureSkipVerify = true
uConfig.SessionTicketsDisabled = true
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
uConn := utls.UClient(conn, uConfig, e.uClient.id)
verifier.UConn = uConn
err := uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
copy(hello.Raw[39:], hello.SessionId)
var nowTime time.Time
if uConfig.Time != nil {
nowTime = uConfig.Time()
} else {
nowTime = time.Now()
}
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1
hello.SessionId[1] = 7
hello.SessionId[2] = 5
copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled {
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
}
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
if authKey == nil {
return nil, E.New("nil auth_key")
}
verifier.authKey = authKey
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
if err != nil {
return nil, err
}
aesBlock, _ := aes.NewCipher(authKey)
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
copy(hello.Raw[39:], hello.SessionId)
if debug.Enabled {
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
}
err = uConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
if debug.Enabled {
fmt.Printf("REALITY Conn.Verified: %v\n", verifier.verified)
}
if !verifier.verified {
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
return nil, E.New("reality verification failed")
}
return &utlsConnWrapper{uConn}, nil
}
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
defer uConn.Close()
client := &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
return uConn, nil
},
},
}
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
request.Header.Set("User-Agent", fingerprint.Client)
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", mRand.Intn(32)+30)})
response, err := client.Do(request)
if err != nil {
return
}
_, _ = io.Copy(io.Discard, response.Body)
response.Body.Close()
}
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.uClient.config.SessionIDGenerator = generator
}
func (e *RealityClientConfig) Clone() Config {
return &RealityClientConfig{
e.uClient.Clone().(*UTLSClientConfig),
e.publicKey,
e.shortID,
}
}
type realityVerifier struct {
*utls.UConn
serverName string
authKey []byte
verified bool
}
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey)
h.Write(pub)
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
c.verified = true
return nil
}
}
opts := x509.VerifyOptions{
DNSName: c.serverName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,192 @@
//go:build with_reality_server
package tls
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"net"
"time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
type RealityServerConfig struct {
config *reality.Config
}
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
var tlsConfig reality.Config
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
}
tlsConfig.Time = router.TimeFunc()
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)
}
}
if options.Certificate != "" || options.CertificatePath != "" {
return nil, E.New("certificate is unavailable in reality")
}
if options.Key != "" || options.KeyPath != "" {
return nil, E.New("key is unavailable in reality")
}
tlsConfig.SessionTicketsDisabled = true
tlsConfig.Type = N.NetworkTCP
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
if err != nil {
return nil, E.Cause(err, "decode private key")
}
if len(privateKey) != 32 {
return nil, E.New("invalid private key")
}
tlsConfig.PrivateKey = privateKey
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range options.Reality.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}
if debug.Enabled {
tlsConfig.Show = true
}
return &RealityServerConfig{&tlsConfig}, nil
}
func (c *RealityServerConfig) ServerName() string {
return c.config.ServerName
}
func (c *RealityServerConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
}
func (c *RealityServerConfig) NextProtos() []string {
return c.config.NextProtos
}
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
}
func (c *RealityServerConfig) Config() (*tls.Config, error) {
return nil, E.New("unsupported usage for reality")
}
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
return ClientHandshake(context.Background(), conn, c)
}
func (c *RealityServerConfig) Start() error {
return nil
}
func (c *RealityServerConfig) Close() error {
return nil
}
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
return ServerHandshake(context.Background(), conn, c)
}
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
tlsConn, err := reality.Server(ctx, conn, c.config)
if err != nil {
return nil, err
}
return &realityConnWrapper{Conn: tlsConn}, nil
}
func (c *RealityServerConfig) Clone() Config {
return &RealityServerConfig{
config: c.config.Clone(),
}
}
var _ Conn = (*realityConnWrapper)(nil)
type realityConnWrapper struct {
*reality.Conn
}
func (c *realityConnWrapper) ConnectionState() ConnectionState {
state := c.Conn.ConnectionState()
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (c *realityConnWrapper) Upstream() any {
return c.Conn
}

View File

@@ -0,0 +1,16 @@
//go:build !with_reality_server
package tls
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
}

View File

@@ -2,36 +2,28 @@ package tls
import (
"context"
"crypto/tls"
"net"
"github.com/sagernet/sing-box/common/badtls"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
return NewSTDServer(ctx, logger, options)
if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, router, logger, options)
} else {
return NewSTDServer(ctx, router, 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
return aTLS.ServerHandshake(ctx, conn, config)
}

View File

@@ -7,6 +7,7 @@ import (
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -35,15 +36,15 @@ 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) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, s.config), nil
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
@@ -57,6 +58,7 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
}
var tlsConfig tls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
return c.config, nil
}
func (c *STDServerConfig) Client(conn net.Conn) Conn {
return tls.Client(conn, c.config)
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, c.config), nil
}
func (c *STDServerConfig) Server(conn net.Conn) Conn {
return tls.Server(conn, c.config)
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
return tls.Server(conn, c.config), nil
}
func (c *STDServerConfig) Clone() Config {
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
return nil
}
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
@@ -175,6 +175,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
} else {
tlsConfig = &tls.Config{}
}
tlsConfig.Time = router.TimeFunc()
if options.ServerName != "" {
tlsConfig.ServerName = options.ServerName
}
@@ -230,7 +231,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(info.ServerName)
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -5,6 +5,7 @@ package tls
import (
"crypto/tls"
"crypto/x509"
"math/rand"
"net"
"net/netip"
"os"
@@ -12,8 +13,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/sagernet/utls"
utls "github.com/refraction-networking/utls"
"golang.org/x/net/http2"
)
type UTLSClientConfig struct {
@@ -34,6 +36,9 @@ func (e *UTLSClientConfig) NextProtos() []string {
}
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
nextProto = append(nextProto, "http/1.1")
}
e.config.NextProtos = nextProto
}
@@ -41,8 +46,19 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS")
}
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
}
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.config.SessionIDGenerator = generator
}
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
}
type utlsConnWrapper struct {
@@ -67,14 +83,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
}
}
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
config: e.config.Clone(),
id: e.id,
}
func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
@@ -88,6 +101,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
}
var tlsConfig utls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
@@ -144,28 +158,59 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
}
tlsConfig.RootCAs = certPool
}
var id utls.ClientHelloID
switch options.UTLS.Fingerprint {
case "chrome", "":
id = utls.HelloChrome_Auto
case "firefox":
id = utls.HelloFirefox_Auto
case "edge":
id = utls.HelloEdge_Auto
case "safari":
id = utls.HelloSafari_Auto
case "360":
id = utls.Hello360_Auto
case "qq":
id = utls.HelloQQ_Auto
case "ios":
id = utls.HelloIOS_Auto
case "android":
id = utls.HelloAndroid_11_OkHttp
case "random":
id = utls.HelloRandomized
default:
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}
var (
randomFingerprint utls.ClientHelloID
randomizedFingerprint utls.ClientHelloID
)
func init() {
modernFingerprints := []utls.ClientHelloID{
utls.HelloChrome_Auto,
utls.HelloFirefox_Auto,
utls.HelloEdge_Auto,
utls.HelloSafari_Auto,
utls.HelloIOS_Auto,
}
randomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]
weights := utls.DefaultWeights
weights.TLSVersMax_Set_VersionTLS13 = 1
weights.FirstKeyShare_Set_CurveP256 = 0
randomizedFingerprint = utls.HelloRandomized
randomizedFingerprint.Seed, _ = utls.NewPRNGSeed()
randomizedFingerprint.Weights = &weights
}
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome", "":
return utls.HelloChrome_Auto, nil
case "firefox":
return utls.HelloFirefox_Auto, nil
case "edge":
return utls.HelloEdge_Auto, nil
case "safari":
return utls.HelloSafari_Auto, nil
case "360":
return utls.Hello360_Auto, nil
case "qq":
return utls.HelloQQ_Auto, nil
case "ios":
return utls.HelloIOS_Auto, nil
case "android":
return utls.HelloAndroid_11_OkHttp, nil
case "random":
return randomFingerprint, nil
case "randomized":
return randomizedFingerprint, nil
default:
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
}
}

View File

@@ -11,3 +11,7 @@ import (
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`)
}
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
}

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,13 +3,28 @@ package constant
import (
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing/common/rw"
)
const dirName = "sing-box"
var resourcePaths []string
var (
basePath string
resourcePaths []string
)
func BasePath(name string) string {
if basePath == "" || strings.HasPrefix(name, "/") {
return name
}
return filepath.Join(basePath, name)
}
func SetBasePath(path string) {
basePath = path
}
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)

3
constant/time.go Normal file
View File

@@ -0,0 +1,3 @@
package constant
const TimeLayout = "2006-01-02 15:04:05 -0700"

View File

@@ -1,3 +1,3 @@
package constant
var Version = "1.1-beta17"
var Version = "unknown"

35
debug.go Normal file
View File

@@ -0,0 +1,35 @@
//go:build go1.19
package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
func applyDebugOptions(options option.DebugOptions) {
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}
if options.MaxStack != nil {
debug.SetMaxStack(*options.MaxStack)
}
if options.MaxThreads != nil {
debug.SetMaxThreads(*options.MaxThreads)
}
if options.PanicOnFault != nil {
debug.SetPanicOnFault(*options.PanicOnFault)
}
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
}
}

35
debug_go118.go Normal file
View File

@@ -0,0 +1,35 @@
//go:build !go1.19
package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
func applyDebugOptions(options option.DebugOptions) {
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}
if options.MaxStack != nil {
debug.SetMaxStack(*options.MaxStack)
}
if options.MaxThreads != nil {
debug.SetMaxThreads(*options.MaxThreads)
}
if options.PanicOnFault != nil {
debug.SetPanicOnFault(*options.PanicOnFault)
}
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
// debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
}
}

37
docs/assets/icon.svg Normal file
View File

@@ -0,0 +1,37 @@
<svg width="1027" height="1109" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden">
<defs>
<filter id="fx0" x="-10%" y="-10%" width="120%" height="120%" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
<feComponentTransfer color-interpolation-filters="sRGB">
<feFuncR type="discrete" tableValues="0 0" />
<feFuncG type="discrete" tableValues="0 0" />
<feFuncB type="discrete" tableValues="0 0" />
<feFuncA type="linear" slope="0.4" intercept="0" />
</feComponentTransfer>
<feGaussianBlur stdDeviation="4.58333 4.58333" />
</filter>
<clipPath id="clip1">
<rect x="692" y="855" width="1027" height="1109" />
</clipPath>
<clipPath id="clip2">
<rect x="-2" y="-2" width="541" height="786" />
</clipPath>
<clipPath id="clip3">
<rect x="0" y="0" width="535" height="782" />
</clipPath>
</defs>
<g clip-path="url(#clip1)" transform="translate(-692 -855)">
<path d="M692 1191 692 1575.69C692 1640.41 731.499 1651.19 731.499 1651.19L1148.03 1931.62C1212.66 1974.77 1194.71 1881.29 1194.71 1881.29L1194.71 1528.96 692 1191Z" fill="#37474F" fill-rule="evenodd" />
<g clip-path="url(#clip2)" filter="url(#fx0)" transform="translate(1184 1182)">
<g clip-path="url(#clip3)">
<path d="M520.482 15.4819 520.482 400.176C520.482 464.89 480.983 475.676 480.983 475.676 480.983 475.676 129.086 712.963 64.4523 756.106-0.181814 799.25 17.7721 705.773 17.7721 705.773L17.7721 353.437 520.482 15.4819Z" fill="#455A64" fill-rule="evenodd" />
</g>
</g>
<path d="M1698 1191 1698 1575.69C1698 1640.41 1658.5 1651.19 1658.5 1651.19 1658.5 1651.19 1306.6 1888.48 1241.97 1931.62 1177.34 1974.77 1195.29 1881.29 1195.29 1881.29L1195.29 1528.96 1698 1191Z" fill="#455A64" fill-rule="evenodd" />
<path d="M1241.71 868.473C1212.96 850.509 1169.85 850.509 1144.7 868.473L713.557 1163.07C684.814 1181.04 684.814 1213.37 713.557 1231.33L1144.7 1529.53C1173.44 1547.49 1216.56 1547.49 1241.71 1529.53L1676.44 1227.74C1705.19 1209.78 1705.19 1177.44 1676.44 1159.48L1241.71 868.473Z" fill="#546E7A" fill-rule="evenodd" />
<path d="M1195 1949C1173.4 1949 1159 1935.19 1159 1917.92L1159 1531.08C1159 1513.82 1173.4 1500 1195 1500 1216.6 1500 1231 1513.82 1231 1531.08L1231 1914.46C1231 1935.19 1216.6 1949 1195 1949Z" fill="#546E7A" fill-rule="evenodd" />
<path d="M1553.92 1435.92C1553.92 1471.89 1557.5 1486.27 1518.03 1511.45L1428.32 1568.99C1388.85 1594.17 1374.5 1572.59 1374.5 1540.22L1374.5 1446.71C1374.5 1439.52 1374.5 1435.92 1363.73 1428.73 1270.43 1363.99 911.591 1115.84 847 1069.09L1012.07 954C1058.72 982.772 1399.61 1209.35 1539.56 1306.45 1546.74 1310.05 1550.33 1317.24 1550.33 1320.84L1550.33 1435.92Z" fill="#99AAB5" fill-rule="evenodd" />
<path d="M1543.41 1310.21C1399.82 1213.17 1058.79 986.752 1015.72 958L951.103 997.534 847 1069.41C911.615 1116.14 1270.59 1360.53 1363.92 1425.22 1371.1 1428.81 1371.1 1432.41 1371.1 1436L1547 1313.8C1547 1313.8 1547 1310.21 1543.41 1310.21Z" fill="#CCD6DD" fill-rule="evenodd" />
<path d="M1554.9 1435.48 1554.9 1324.19C1554.9 1317.01 1551.3 1313.42 1544.11 1309.83 1400.28 1212.89 1058.67 986.721 1015.51 958L940 1008.26C1062.26 1090.83 1389.49 1306.24 1475.79 1367.27 1486.58 1374.45 1486.58 1381.63 1486.58 1385.22L1486.58 1536 1522.54 1510.87C1558.5 1485.74 1554.9 1467.79 1554.9 1435.48Z" fill="#CCD6DD" fill-rule="evenodd" />
<path d="M1543.23 1309.95C1399.6 1212.98 1058.49 986.731 1015.4 958L940 1008.28C1062.08 1090.88 1388.83 1306.36 1475.01 1367.41 1475.01 1367.41 1478.6 1371 1478.6 1371L1554 1317.13C1546.82 1313.54 1546.82 1309.95 1543.23 1309.95Z" fill="#E1E8ED" fill-rule="evenodd" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,10 +1,219 @@
#### 1.2.2
* Accept `any` outbound in dns rule **1**
* Fix bugs and update dependencies
*1*:
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains to `domain` rule.
#### 1.2.1
* Fix missing default host in v2ray http transport`s request
* Flush DNS cache for macOS when tun start/close
* Fix tun's DNS hijacking compatibility with systemd-resolved
#### 1.2.0
* Fix bugs and update dependencies
Important changes since 1.1:
* Introducing our [new iOS client application](/installation/clients/sfi)
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Add [NTP service](/configuration/ntp)
* 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 fallback support for v2ray transport
* Add custom TLS server support for http based v2ray transports
* Add health check support for http-based v2ray transports
* Add multiple configuration support
#### 1.2-rc1
* Fix bugs and update dependencies
#### 1.2-beta10
* Add multiple configuration support **1**
* Fix bugs and update dependencies
*1*:
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
to load all configuration files in a directory.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
name.
#### 1.1.7
* Improve the stability of the VMESS server
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
* Fix bugs and update dependencies
#### 1.2-beta9
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add health check support for http-based v2ray transports
* Remove length limit on short_id for reality TLS config
* Fix bugs and update dependencies
#### 1.2-beta8
* Update reality and uTLS libraries
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
#### 1.2-beta7
* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client
* Improve the stability of the VMESS server
#### 1.2-beta6
* Introducing our [new iOS client application](/installation/clients/sfi)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add custom TLS server support for http based v2ray transports
* Add generate commands
* Enable XUDP by default in VLESS
* Update reality server
* Update vision protocol
* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)
* Bug fixes
* Update dependencies
#### 1.2-beta5
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Fix match private address
#### 1.1.6
* Improve vmess request
* Fix ipv6 redirect on Linux
* Fix match geoip private
* Fix parse hysteria UDP message
* Fix socks connect response
* Disable vmess header protection if transport enabled
* Update QUIC v2 version number and initial salt
#### 1.2-beta4
* Add [NTP service](/configuration/ntp)
* Add Add multiple server names and multi-user support for shadowtls
* Add strict mode support for shadowtls v3
* Add uTLS support for shadowtls v3
#### 1.2-beta3
* Update QUIC v2 version number and initial salt
* Fix shadowtls v3 implementation
#### 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) options.
Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
#### 1.0.7

View File

@@ -9,6 +9,11 @@
"mixed-in"
],
"ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp",
"auth_user": [
"usera",
@@ -119,6 +124,10 @@ Tags of [Inbound](/configuration/inbound).
Not limited if empty.
#### query_type
DNS query type. Values can be integers or type name strings.
#### network
`tcp` or `udp`.
@@ -223,6 +232,8 @@ Invert match result.
Match outbound.
`any` can be used as a value to match any outbound.
#### server
==Required==
@@ -245,18 +256,4 @@ Disable cache and save cache in this query.
#### rules
Included default rules.
#### invert
Invert match result.
#### server
==Required==
Tag of the target dns server.
#### disable_cache
Disable cache and save cache in this query.
Included default rules.

View File

@@ -9,6 +9,11 @@
"mixed-in"
],
"ip_version": 6,
"query_type": [
"A",
"HTTPS",
32768
],
"network": "tcp",
"auth_user": [
"usera",
@@ -118,6 +123,10 @@
默认不限制。
#### query_type
DNS 查询类型。值可以为整数或者类型名称字符串。
#### network
`tcp``udp`
@@ -222,6 +231,8 @@
匹配出站。
`any` 可作为值用于匹配任意出站。
#### server
==必填==
@@ -244,18 +255,4 @@
#### rules
包括的默认规则。
#### invert
反选匹配结果。
#### server
==必填==
目标 DNS 服务器的标签。
#### disable_cache
在此查询中禁用缓存。
包括的默认规则。

View File

@@ -30,16 +30,17 @@ The tag of the dns server.
The address of the dns server.
| Protocol | Format |
|----------|-----------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| Protocol | Format |
|----------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
!!! 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.
!!! warning ""
DHCP transport is not included by default, see [Installation](/#installation).
| RCode | Description |
|-------------------|-----------------------|
| `success` | `No error` |

View File

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

View File

@@ -9,7 +9,6 @@
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"secret": "",
"direct_io": false,
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
@@ -18,13 +17,15 @@
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"direct_io": false,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
@@ -58,10 +59,6 @@ Secret for the RESTful API (optional)
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
#### direct_io
Allows lossless relays like splice without real-time traffic reporting.
#### default_mode
Default mode in clash, `rule` will be used if empty.
@@ -98,10 +95,6 @@ Traffic statistics service settings.
Enable statistics service.
#### stats.direct_io
Allows lossless relays like splice without real-time traffic reporting.
#### stats.inbounds
Inbound list to count traffic.
@@ -109,3 +102,7 @@ Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@@ -9,7 +9,6 @@
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"secret": "",
"direct_io": false,
"default_mode": "rule",
"store_selected": false,
"cache_file": "cache.db"
@@ -18,13 +17,15 @@
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"direct_io": false,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
@@ -56,10 +57,6 @@ RESTful API 的密钥(可选)
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
#### direct_io
允许像 splice 这样的没有实时流量报告的无损中继。
#### default_mode
Clash 中的默认模式,默认使用 `rule`
@@ -96,10 +93,6 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
启用统计服务。
#### stats.direct_io
允许像 splice 这样的没有实时流量报告的无损中继。
#### stats.inbounds
统计流量的入站列表。
@@ -107,3 +100,7 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

@@ -40,4 +40,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -40,4 +40,8 @@ HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -74,14 +74,10 @@ Hysteria users
#### users.auth
==Required if `auth_str` is empty==
Authentication password, in base64.
#### users.auth_str
==Required if `auth` is empty==
Authentication password.
#### recv_window_conn

View File

@@ -74,14 +74,10 @@ Hysteria 用户
#### users.auth
==与 auth_str 必填一个==
base64 编码的认证密码。
#### users.auth_str
==与 auth 必填一个==
认证密码。
#### recv_window_conn

View File

@@ -26,6 +26,8 @@
| `trojan` | [Trojan](./trojan) | TCP |
| `naive` | [Naive](./naive) | X |
| `hysteria` | [Hysteria](./hysteria) | X |
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
| `vless` | [VLESS](./vless) | TCP |
| `tun` | [Tun](./tun) | X |
| `redirect` | [Redirect](./redirect) | X |
| `tproxy` | [TProxy](./tproxy) | X |

View File

@@ -37,4 +37,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
Automatically set system proxy configuration when start and clean up when stop.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -37,4 +37,8 @@ SOCKS 和 HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -77,11 +77,11 @@ Both if empty.
==Required==
| Method | Password Format |
|---------------|-------------------------------------|
| none | / |
| 2022 methods | `openssl rand -base64 <Key Length>` |
| other methods | any string |
| Method | Password Format |
|---------------|------------------------------------------------|
| none | / |
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
| other methods | any string |
### Listen Fields

View File

@@ -77,8 +77,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
==必填==
| 方法 | 密码格式 |
|---------------|-------------------------------|
| none | / |
| 2022 methods | `openssl rand -base64 <密钥长度>` |
| other methods | 任意字符串 |
| 方法 | 密码格式 |
|---------------|------------------------------------------|
| none | / |
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
| other methods | 任意字符串 |

View File

@@ -7,15 +7,29 @@
... // Listen Fields
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"fallback_after": 2,
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"handshake": {
"server": "google.com",
"server_port": 443,
... // Dial Fields
}
},
"handshake_for_server_name": {
"example.com": {
"server": "example.com",
"server_port": 443,
... // Dial Fields
}
},
"strict_mode": false
}
```
@@ -33,24 +47,35 @@ ShadowTLS 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.
ShadowTLS password.
Only available in the ShadowTLS v2 protocol.
Only available in the ShadowTLS protocol 2.
#### fallback_after
#### users
Packet count before perform fallback.
ShadowTLS users.
Default is 2.
Lowering this may prevent TLS 1.3 connections, but reduces the risk of being actively probed.
Only available in the ShadowTLS protocol 3.
#### handshake
==Required==
Handshake server address and [Dial options](/configuration/shared/dial).
Handshake server address and [Dial options](/configuration/shared/dial).
#### handshake_for_server_name
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name.
Only available in the ShadowTLS protocol 2/3.
#### strict_mode
ShadowTLS strict mode.
Only available in the ShadowTLS protocol 3.

View File

@@ -7,15 +7,29 @@
... // 监听字段
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"fallback_after": 2,
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"handshake": {
"server": "google.com",
"server_port": 443,
... // 拨号字段
}
},
"handshake_for_server_name": {
"example.com": {
"server": "example.com",
"server_port": 443,
... // 拨号字段
}
},
"strict_mode": false
}
```
@@ -33,23 +47,36 @@ 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 密码。
仅在 ShadowTLS v2 协议中可用。
仅在 ShadowTLS 协议版本 2 中可用。
#### fallback_after
#### users
在执行回退之前的包计数
ShadowTLS 用户
默认值为 2
降低此值可能会阻止 TLS 1.3 连接,但会降低被主动探测的风险。
仅在 ShadowTLS 协议版本 3 中可用
#### handshake
==必填==
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
#### handshake_for_server_name
==必填==
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
仅在 ShadowTLS 协议版本 2/3 中可用。
#### strict_mode
ShadowTLS 严格模式。
仅在 ShadowTLS 协议版本 3 中可用。

View File

@@ -46,8 +46,15 @@
"exclude_package": [
"com.android.captiveportallogin"
],
...
// Listen Fields
"platform": {
"http_proxy": {
"enabled": false,
"server": "127.0.0.1",
"server_port": 8080
}
},
... // Listen Fields
}
```
@@ -100,8 +107,7 @@ Enforce strict routing rules when `auto_route` is enabled:
* Let unsupported network unreachable
* Route all connections to tun
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
not be accessible by others.
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
*In Windows*:
@@ -187,6 +193,14 @@ Limit android packages in route.
Exclude android packages in route.
#### platform
Platform-specific settings, provided by client applications.
#### platform.http_proxy
System HTTP proxy settings.
### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details.

View File

@@ -46,8 +46,15 @@
"exclude_package": [
"com.android.captiveportallogin"
],
...
// 监听字段
"platform": {
"http_proxy": {
"enabled": false,
"server": "127.0.0.1",
"server_port": 8080
}
},
... // 监听字段
}
```
@@ -100,7 +107,7 @@ tun 接口的 IPv6 前缀。
* 让不支持的网络无法到达
* 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
*在 Windows 中*:
@@ -148,19 +155,19 @@ TCP/IP 栈。
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`
限制被路由的用户。默认不限制。
限制被路由的用户。默认不限制。
#### include_uid_range
限制被路由的用户范围。
限制被路由的用户范围。
#### exclude_uid
排除路由的用户。
排除路由的用户。
#### exclude_uid_range
排除路由的用户范围。
排除路由的用户范围。
#### include_android_user
@@ -183,6 +190,14 @@ TCP/IP 栈。
排除路由的 Android 应用包名。
#### platform
平台特定的设置,由客户端应用提供。
#### platform.http_proxy
系统 HTTP 代理设置。
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@@ -0,0 +1,54 @@
### Structure
```json
{
"type": "vless",
"tag": "vless-in",
... // Listen Fields
"users": [
{
"name": "sekai",
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
"flow": ""
}
],
"tls": {},
"transport": {}
}
```
### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### users
==Required==
VLESS users.
#### users.uuid
==Required==
VLESS user id.
#### users.flow
VLESS Sub-protocol.
Available values:
* `xtls-rprx-vision`
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

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