Compare commits

...

473 Commits

Author SHA1 Message Date
世界
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
世界
ffd54eef6c Update documentation 2022-11-21 21:20:44 +08:00
世界
c16e4316d6 Fix shadowtls server 2022-11-21 21:20:44 +08:00
世界
8b7fe20b7f Include uTLS in release 2022-11-21 15:25:49 +08:00
世界
696c1065b6 Update stable documentation 2022-11-21 14:57:22 +08:00
世界
5d690f4147 Update documentation 2022-11-21 13:18:04 +08:00
世界
f906641a82 Add uTLS to makefile default tags 2022-11-21 13:18:04 +08:00
世界
89913dfa8c Improve shadowtls server 2022-11-21 13:18:04 +08:00
世界
468778f67f Update dependencies 2022-11-21 13:18:04 +08:00
世界
22a22aebe2 Fix default dns transport strategy 2022-11-21 13:18:04 +08:00
世界
a2d2ec9b45 Update documentation 2022-11-15 17:36:42 +08:00
世界
2695b3516e Update issue template 2022-11-13 11:45:24 +08:00
世界
3a9ef8fac0 Remove unused 2022-11-13 11:30:48 +08:00
世界
ebad363201 Fix create TLS config 2022-11-13 11:24:37 +08:00
世界
11076d52cd Fix dns buffer & quic retry 2022-11-13 11:16:10 +08:00
世界
5eb132063e Fix connect packet connection for mux client 2022-11-12 03:53:42 +08:00
世界
13ab5d3348 Remove follow in update script 2022-11-11 22:32:24 +08:00
世界
ce1ddc400f Support x/h2 v0.2.0 deadline 2022-11-11 22:08:20 +08:00
arm64v8a
2c9d25e853 Fix websocket alpn 2022-11-11 20:01:49 +08:00
世界
3d76777760 Fix tor geoip 2022-11-10 22:42:05 +08:00
世界
24f4dfea04 Fix hysteria test 2022-11-10 21:10:18 +08:00
世界
2fc1a0a9dd Update documentation 2022-11-10 16:33:10 +08:00
世界
617aba84e4 Add multi user support for hysteria inbound 2022-11-09 21:00:08 +08:00
世界
5510c474c7 Fix h2c transport 2022-11-09 12:15:14 +08:00
世界
eb2e8a0b40 Add custom tls client support for std grpc 2022-11-09 11:46:29 +08:00
世界
972491c19d Fix default local DNS server behavior 2022-11-09 10:35:16 +08:00
世界
7358ca4a52 Fix vmess request buffer 2022-11-09 10:16:22 +08:00
世界
61c274045a Update install go script 2022-11-08 23:19:53 +08:00
世界
f205140b04 Fix smux keep alive 2022-11-08 16:45:38 +08:00
世界
1db8e03c86 Fix format 2022-11-08 14:54:19 +08:00
世界
2ecf86c2bc Update patched quic-go 2022-11-08 13:54:01 +08:00
世界
999a847e86 Add custom wireguard worker size option 2022-11-08 13:48:14 +08:00
世界
1f63ce5dee Fix reset outbound 2022-11-06 10:36:19 +08:00
世界
0ad1bbea11 Fix wireguard close 2022-11-06 10:20:23 +08:00
世界
b2cd78d279 Move WFP manipulation to strict route 2022-11-06 10:16:07 +08:00
世界
d5bb58a0b4 Update documentation 2022-11-06 10:16:07 +08:00
世界
7f84936050 Split bind_address 2022-11-06 10:16:07 +08:00
Dreamacro
6adfea0a72 Fix macOS Ventura process name match 2022-11-06 10:16:07 +08:00
Hellojack
10f213bf3d Adjust uTLS wrapper 2022-11-06 10:16:07 +08:00
世界
6e8c4f6576 Update documentation 2022-10-31 13:59:52 +08:00
世界
9779dc0154 Fix test 2022-10-31 13:59:52 +08:00
世界
a2abe31298 Fix uTLS config 2022-10-31 13:59:52 +08:00
世界
930d177dd0 Update dependencies 2022-10-31 13:59:52 +08:00
Fei1Yang
f3d1b59173 Update container action 2022-10-29 18:01:32 +08:00
世界
14452f3049 Update documentation 2022-10-29 18:00:05 +08:00
世界
4119c8647b Update dependencies 2022-10-29 17:56:21 +08:00
世界
90a94a8c63 Improve local dns transport 2022-10-29 17:37:11 +08:00
世界
b0c39ac7ff Suppress no network error 2022-10-28 09:54:04 +08:00
世界
8703e1ff98 Fix decrypt xplus packet 2022-10-28 09:53:57 +08:00
世界
35886b88d7 Add option for custom wireguard reserved bytes 2022-10-28 09:53:57 +08:00
永雏塔菲
d583b35717 Add s390x architecture support
* Update debug.yml

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

* Update documentation
2022-10-28 09:53:57 +08:00
世界
22f06f582b Fix v2ray api 2022-10-26 20:06:13 +08:00
世界
f2b5098fa0 Update documentation 2022-10-25 21:26:28 +08:00
世界
0ca3290364 Add go1.18 debug build 2022-10-25 21:25:55 +08:00
世界
43d5b8598b Fix shadowtls conn 2022-10-25 21:25:42 +08:00
世界
f3e1d1defc Fix h3 dns transport 2022-10-20 11:04:03 +08:00
世界
95c03c9373 Fix copy pipe 2022-10-20 10:57:57 +08:00
世界
7e0958b4ac Update documentation 2022-10-19 10:55:06 +08:00
Skyxim
6a26737508 Check destination before udp connect 2022-10-19 10:22:46 +08:00
世界
92a92f39c5 Fix naive overflow 2022-10-18 17:52:52 +08:00
世界
fc533cd38d Fix DF for hysteria 2022-10-18 17:27:50 +08:00
世界
68e286499d Update dependencies 2022-10-18 17:27:50 +08:00
世界
f5c1900aad Add message for tfo error 2022-10-18 17:27:50 +08:00
世界
6591dd58ca Remove strict route on windows
replaced by custom route
2022-10-12 16:24:45 +08:00
XYenon
54af113363 Add custom route support (#147) 2022-10-12 16:20:17 +08:00
世界
3f1fe814ef Fix sniff fragmented quic client hello 2022-10-12 16:11:42 +08:00
世界
5a2cebebd1 Remove unused 2022-10-10 14:23:34 +08:00
世界
b8009d61b2 Fix tfo headroom 2022-10-10 13:33:48 +08:00
世界
a61a64bf9e Add shadowtls inbound test 2022-10-10 11:31:03 +08:00
世界
7d17c52fea Add more messages to darwin route error 2022-10-09 21:22:07 +08:00
世界
f5b15b392b Fix ssh outbound 2022-10-09 20:43:01 +08:00
世界
8a53846efd Fix uTLS handshake 2022-10-08 20:31:01 +08:00
世界
badc454452 Fix test 2022-10-08 20:30:52 +08:00
世界
a01bb569d1 Fix websocket headroom 2022-10-08 20:09:36 +08:00
世界
89ff9f8368 Fix interface monitor 2022-10-08 20:09:36 +08:00
世界
7f816a2ebc Add sniff_timeout 2022-10-08 20:09:36 +08:00
世界
39c141651a Update documentation 2022-10-06 23:33:57 +08:00
世界
b0ad9bb6f1 Add shadowtls v2 support 2022-10-06 22:47:11 +08:00
世界
d135d0f287 Update tfo-go usage 2022-10-06 21:58:50 +08:00
世界
b183ccf23d Fix wfp filter weight 2022-10-05 20:24:27 +08:00
世界
c2969bc186 Update documentation 2022-10-03 04:36:54 +08:00
世界
bd86bfcd22 Fix check system stack packet 2022-10-03 04:36:54 +08:00
世界
8aec64b855 Add v2ray mux support for all connections 2022-10-03 04:34:59 +08:00
世界
1445bdba37 Fix trojan fallback 2022-10-01 11:41:15 +08:00
世界
29d08e63b5 Fix clash tracker 2022-10-01 11:29:46 +08:00
世界
1173fdea64 Improve tls writer 2022-10-01 11:29:46 +08:00
世界
968430c338 Minor fixes 2022-09-30 21:08:07 +08:00
世界
3e5bee6faf Fix windows route 2022-09-30 00:36:42 +08:00
世界
aa613cba73 Fix dns close 2022-09-29 09:12:13 +08:00
世界
1e510511ae Fix random seed 2022-09-29 08:49:34 +08:00
世界
1b44faed17 Add v2ray stats api 2022-09-29 08:49:34 +08:00
世界
c7a485815c Add binary to .gitignore 2022-09-26 19:36:51 +08:00
世界
7f9c870bba Add direct io option for clash api 2022-09-26 15:31:02 +08:00
世界
b5564ef3d3 Fix bind control 2022-09-26 13:50:54 +08:00
世界
8ce244dd04 Fix documentation
Signed-off-by: 世界 <i@sekai.icu>
Signed-off-by: unknowndevQwQ <unknowndevQwQ@pm.me>
2022-09-26 12:25:18 +08:00
世界
0f57b93925 Update documentation 2022-09-25 22:29:18 +08:00
世界
c90a77a185 Refine 4in6 processing 2022-09-25 22:29:18 +08:00
世界
c6586f19fa Fix read source address from grpc-go 2022-09-25 22:29:18 +08:00
世界
cbab86ae38 Refine tproxy write back 2022-09-25 22:29:18 +08:00
世界
17b5f031f1 Fix shadowsocks plugins 2022-09-25 16:43:12 +08:00
世界
b00b6b9e25 Fix fqdn socks5 outbound connection 2022-09-25 14:42:39 +08:00
世界
fb6b3b0401 Fix missing source address from transport connection 2022-09-23 18:55:28 +08:00
世界
22ea878fe9 Improve websocket writer 2022-09-23 18:55:07 +08:00
世界
abe3dc6039 Add self sign cert support 2022-09-23 17:13:18 +08:00
世界
852829b9dc Add VMess benchmark result 2022-09-23 16:13:29 +08:00
世界
407509c985 Fix leaks and add test 2022-09-23 13:14:31 +08:00
世界
9856b73cb5 Update documentation 2022-09-23 10:30:07 +08:00
世界
f42356fbcb Fix system stack ipv4 overflow 2022-09-23 10:29:15 +08:00
世界
d0b467671a Merge VLESS to library 2022-09-23 10:28:51 +08:00
世界
c18c545798 Add stdio test 2022-09-23 10:28:24 +08:00
世界
693ef293ac Update buffer usage 2022-09-23 10:27:48 +08:00
世界
a006627795 Disable DF on direct outbound by default 2022-09-23 10:27:46 +08:00
世界
0738b184e4 Fix url test interval 2022-09-23 10:27:42 +08:00
世界
42524ba04e Fix dns sniffer 2022-09-17 16:59:28 +08:00
世界
63fc95b96d Add mux server and XUDP client for VMess 2022-09-17 11:54:04 +08:00
世界
ab436fc137 Update documentation 2022-09-16 15:48:31 +08:00
世界
1546770bfd Skip bind on local addr 2022-09-16 15:35:29 +08:00
世界
f4b2099488 Fix tun log 2022-09-16 15:32:50 +08:00
世界
a2c4d68031 Fix create UDP transport 2022-09-15 16:46:53 +08:00
世界
cfe14f2817 Suppress bad http2 error 2022-09-15 15:34:52 +08:00
世界
a5402ffb69 Add back urltest outbound 2022-09-15 15:22:08 +08:00
世界
4d24cf5ec4 Update documentation 2022-09-15 13:25:51 +08:00
世界
668d354771 Make gVisor optional 2022-09-15 12:24:08 +08:00
世界
ad14719b14 Fix clash api proxy type 2022-09-14 23:02:11 +08:00
世界
d9aa0a67d6 Fix port rule match logic 2022-09-14 22:03:26 +08:00
世界
92bf784f4f Move shadowsocksr implementation to clash 2022-09-14 21:57:40 +08:00
世界
395b13103a Fix test 2022-09-14 18:02:51 +08:00
世界
628cf56d3c Fix close grpc conn 2022-09-14 18:02:37 +08:00
世界
ac5582537f Add back test workflow 2022-09-14 18:02:37 +08:00
世界
9aa7a20d96 Print tags in version command 2022-09-14 18:02:37 +08:00
世界
189f02c802 Refactor bind control 2022-09-14 18:02:37 +08:00
世界
2373281c41 Fix clash store-selected 2022-09-13 17:34:37 +08:00
世界
e8f4c2d36f Redirect clash hello to external ui 2022-09-13 17:29:57 +08:00
世界
07b6db23c1 Update install go script 2022-09-13 16:24:44 +08:00
世界
9a3360e5d0 Fix build on go1.18 2022-09-13 16:23:20 +08:00
世界
007a278ac8 Refactor to miekg/dns 2022-09-13 16:18:39 +08:00
世界
1db7f45370 Update documentation 2022-09-13 11:24:33 +08:00
世界
b271e19a23 Fix concurrent write 2022-09-13 10:41:10 +08:00
世界
79b6bdfda1 Skip wait for hysteria tcp handshake response
Co-authored-by: arm64v8a <48624112+arm64v8a@users.noreply.github.com>
2022-09-13 10:40:26 +08:00
世界
38088f28b0 Add vless outbound and xudp 2022-09-12 21:59:27 +08:00
世界
dfb8b5f2fa Fix hysteria inbound 2022-09-12 18:35:36 +08:00
世界
9913e0e025 Add shadowsocksr outbound 2022-09-12 18:35:36 +08:00
世界
ce567ffdde Add obfs-local and v2ray-plugin support for shadowsocks outbound 2022-09-12 14:55:00 +08:00
世界
5a9913eca5 Fix socks4 client 2022-09-12 11:33:38 +08:00
世界
eaf1ace681 Update documentation 2022-09-11 22:48:42 +08:00
世界
a2d1f89922 Add custom tls client support for v2ray h2/grpclite transports 2022-09-11 22:44:35 +08:00
世界
7e09beb0c3 Minor fixes 2022-09-11 22:44:35 +08:00
世界
ebf5cbf1b9 Update documentation 2022-09-10 23:31:07 +08:00
世界
d727710d60 Run build on main branch 2022-09-10 22:54:53 +08:00
世界
0e31aeea00 Fix socks4 request 2022-09-10 22:54:50 +08:00
世界
2f437a0382 Add uTLS client 2022-09-10 22:10:45 +08:00
世界
3ad4370fa5 Add ECH TLS client 2022-09-10 22:10:45 +08:00
世界
a3bb9c2877 Import cloudflare tls 2022-09-10 22:10:45 +08:00
世界
ee7e976084 Refactor TLS 2022-09-10 22:10:45 +08:00
世界
099358d3e5 Add clash persistence support 2022-09-10 14:42:14 +08:00
世界
5297273937 Add clash mode support 2022-09-10 14:15:11 +08:00
世界
80cfc9a25b Fix processing empty dns result 2022-09-10 14:15:11 +08:00
世界
2ae4da524e Fix tun documentation 2022-09-10 10:21:42 +08:00
世界
bbe7f28545 Fix system stack crash 2022-09-09 19:44:13 +08:00
世界
78ddd497ee Fix no_gvisor build 2022-09-09 19:44:13 +08:00
世界
8d044232af Update documentation 2022-09-09 15:42:33 +08:00
世界
aa7e85caa7 Update dependencies
Add half close for smux
Update gVisor to 20220905.0
2022-09-09 14:44:18 +08:00
zakuwaki
46a8f24400 Optional proxyproto header 2022-09-09 14:44:18 +08:00
世界
87bc292296 Add comment filter for config 2022-09-09 14:44:18 +08:00
世界
ac539ace70 Add system tun stack 2022-09-09 14:44:18 +08:00
世界
a15b13978f Set default tun mtu to 9000 like clash
IDK why, maybe faster in a local speed test?
2022-09-09 14:44:18 +08:00
世界
0c975db0a6 Set udp dontfrag by default 2022-09-09 14:44:18 +08:00
世界
cb4fea0240 Refactor wireguard & add tun support 2022-09-09 14:44:18 +08:00
世界
8e7957d440 Add support for use with android VPNService 2022-09-09 14:44:18 +08:00
世界
f7bed32c6f Bump version 2022-09-09 14:43:42 +08:00
世界
ef7f2d82c0 Fix match 4in6 address in ip_cidr 2022-09-09 14:07:02 +08:00
世界
7aa97a332e Fix documentation 2022-09-09 13:54:02 +08:00
世界
7c30dde96b Minor fixes 2022-09-08 18:33:59 +08:00
GyDi
9cef2a0a8f Fix clashapi log level format error 2022-09-08 18:04:06 +08:00
世界
f376683fc3 Update documentation 2022-09-07 23:10:36 +08:00
世界
4b61d6e875 Fix hysteria stream error 2022-09-07 19:16:20 +08:00
世界
7d83e350fd Refine test 2022-09-07 19:16:20 +08:00
世界
500ba69548 Fix processing vmess termination signal 2022-09-07 19:16:20 +08:00
世界
9a422549b1 Fix json format error message 2022-09-07 13:23:26 +08:00
世界
3b48fa455e Fix naive inbound temporary 2022-09-07 12:30:54 +08:00
zakuwaki
ef013e0639 Suppress accept proxyproto failed #65 2022-09-06 23:16:31 +08:00
世界
8f8437a88d Fix wireguard reconnect 2022-09-06 00:11:43 +08:00
世界
1b091c9b07 Update documentation 2022-09-04 13:15:10 +08:00
世界
4801b6f057 Fix DNS routing 2022-09-04 12:49:38 +08:00
世界
9078bc2de5 Fix write trojan udp 2022-09-03 16:58:55 +08:00
世界
b69464dfe9 Update documentation for dial fields 2022-09-03 13:02:41 +08:00
世界
62fa48293a Merge dialer options 2022-09-03 12:55:10 +08:00
世界
b206d0889b Fix dial parallel in direct outbound 2022-09-03 12:01:48 +08:00
世界
ee691d81bf Fix write zero 2022-09-03 09:25:30 +08:00
void aire()
56876a67cc Fix documentation typo (#60) 2022-09-02 19:04:03 +08:00
世界
4a0df713aa Add ws compatibility test 2022-09-01 20:32:47 +08:00
世界
ef801cbfbe Fix server install script 2022-09-01 20:32:47 +08:00
世界
9378fc88d2 Add with_wireguard to default server tag 2022-09-01 20:16:20 +08:00
世界
f46bfcc3d8 Move unstable branch to dev-next 2022-08-31 23:45:42 +08:00
0x7d274284
ccdb238843 Fix documentation typo (#57) 2022-08-31 23:42:36 +08:00
世界
f1f61b4e2b Fix install documentation 2022-08-31 23:37:30 +08:00
世界
a44cb745d9 Fix write log timestamp 2022-08-31 23:35:43 +08:00
世界
f5f5cb023c Update documentation 2022-08-31 14:34:32 +08:00
世界
5813e0ce7a Add shadowtls (#49)
* Add shadowtls outbound

* Add shadowtls inbound

* Add shadowtls example

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

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

View File

@@ -1,6 +1,5 @@
name: Bug Report
description: "Create a report to help us improve."
labels: [ bug ]
body:
- type: checkboxes
id: terms
@@ -9,9 +8,11 @@ body:
options:
- label: Yes, I'm using the latest major release. Only such installations are supported.
required: true
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true
- label: Yes, I've included all information below (version, config, etc).
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
required: true
- type: textarea
@@ -30,7 +31,7 @@ body:
<details>
```console
$ sing-box --version
$ sing-box version
# Paste output here
```
@@ -51,4 +52,19 @@ body:
</details>
validations:
required: true
required: true
- type: textarea
id: log
attributes:
label: Server and client log file
value: |-
<details>
```console
# paste log here
```
</details>
validations:
required: true

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

@@ -1,13 +1,5 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-tun rev-parse HEAD)
go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD)
go get -x github.com/sagernet/sing-vmess@$(git -C $PROJECTS/sing-vmess rev-parse HEAD)
go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)
go mod tidy
pushd test
go mod tidy
popd

View File

@@ -3,38 +3,36 @@ name: Debug build
on:
push:
branches:
- dev
- main-next
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- dev
- main-next
- dev-next
jobs:
build:
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@v3
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
@@ -47,9 +45,30 @@ jobs:
go mod init build
go get -v github.com/sagernet/sing-box@$version
popd
continue-on-error: true
- name: Run Test
run: |
go test -v ./...
build_go118:
name: Debug build (Go 1.18)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make
cross:
strategy:
matrix:
@@ -123,6 +142,9 @@ jobs:
- name: linux-mips64el
goos: linux
goarch: mips64le
- name: linux-s390x
goos: linux
goarch: s390x
# darwin
- name: darwin-amd64
goos: darwin
@@ -160,39 +182,31 @@ jobs:
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
CGO_ENABLED: 0
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@v3
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: Build
id: build
run: |
VERSION="$(date +%Y%m%d).$(git rev-parse --short HEAD)"
BUILDTIME="$(LANG=en_US.UTF-8 date -u)"
go build -v -trimpath -ldflags '\
-X "github.com/sagernet/sing-box/constant.Version=$VERSION" \
-X "github.com/sagernet/sing-box/constant.BuildTime=$BUILDTIME" \
-s -w -buildid=' ./cmd/sing-box
echo "::set-output name=VERSION::$VERSION"
run: make
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sing-box-${{ matrix.name }}-${{ steps.build.outputs.VERSION }}
path: sing-box*
name: sing-box-${{ matrix.name }}
path: sing-box*

45
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Build Docker Images
on:
workflow_dispatch:
inputs:
tag:
description: "The tag version you want to build"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Docker Buildx
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@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v4
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
id: tag
run: |
echo "latest=ghcr.io/sagernet/sing-box:latest" >> $GITHUB_OUTPUT
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.ref_name }}" >> $GITHUB_OUTPUT
else
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@v4
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist
tags: |
${{ steps.tag.outputs.latest }}
${{ steps.tag.outputs.versioned }}
push: true

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

15
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@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
days-before-close: 5

10
.gitignore vendored
View File

@@ -3,4 +3,12 @@
/*.json
/*.db
/site/
/bin/
/bin/
/dist/
/sing-box
/sing-box.exe
/build/
/*.jar
/*.aar
/*.xcframework/
.DS_Store

View File

@@ -7,14 +7,20 @@ linters:
- staticcheck
- paralleltest
issues:
fix: true
run:
skip-dirs:
- transport/simple-obfs
- transport/clashssr
- transport/cloudflaretls
- transport/shadowtls/tls
- transport/shadowtls/tls_go119
linters-settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/sagernet/)
- default
staticcheck:
go: '1.18'
go: '1.20'

137
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,137 @@
project_name: sing-box
builds:
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- linux_amd64_v1
- linux_amd64_v3
- linux_arm64
- linux_arm_7
- linux_s390x
- windows_amd64_v1
- windows_amd64_v3
- windows_386
- windows_arm64
- darwin_amd64_v1
- 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:
- -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:
- id: archive
format: tar.gz
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
nfpms:
- id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
vendor: sagernet
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.service
dst: /etc/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postinstall: release/config/postinstall.sh
postremove: release/config/postremove.sh
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
checksum:
disable: true
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
signs:
- artifacts: checksum
release:
github:
owner: SagerNet
name: sing-box
name_template: '{{ if .IsSnapshot }}{{ nightly }}{{ else }}{{ .Version }}{{ end }}'
draft: true
mode: replace

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
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
ARG GOPROXY=""
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
-o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

89
Makefile Normal file
View File

@@ -0,0 +1,89 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
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
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
MAIN = ./cmd/sing-box
.PHONY: test release
build:
go build $(PARAMS) $(MAIN)
install:
go install $(PARAMS) $(MAIN)
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write --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@latest
lint:
GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
proto:
@go run ./cmd/internal/protogen
@gofumpt -l -w .
@gofumpt -l -w .
proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot:
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:
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
rm -r dist
release_install:
go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest
test:
@go test -v ./... && \
cd test && \
go mod tidy && \
go test -v -tags "$(TAGS_TEST)" .
test_stdio:
@go test -v ./... && \
cd test && \
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
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx

View File

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

View File

@@ -2,11 +2,13 @@ package adapter
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Inbound interface {
@@ -15,9 +17,17 @@ type Inbound interface {
Tag() string
}
type InjectableInbound interface {
Inbound
Network() []string
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type InboundContext struct {
Inbound string
InboundType string
IPVersion int
Network string
Source M.Socksaddr
Destination M.Socksaddr
@@ -28,14 +38,18 @@ type InboundContext struct {
// cache
DomainStrategy dns.DomainStrategy
SniffEnabled bool
SniffOverrideDestination bool
DestinationAddresses []netip.Addr
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
InboundOptions option.InboundOptions
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
// dns cache
QueryType uint16
}
type inboundContextKey struct{}

View File

@@ -11,7 +11,7 @@ import (
"github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/dns/dnsmessage"
mdns "github.com/miekg/dns"
)
type Router interface {
@@ -27,18 +27,41 @@ type Router interface {
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
InterfaceBindManager() control.BindManager
InterfaceFinder() control.InterfaceFinder
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
SetTrafficController(controller TrafficController)
TimeService
ClashServer() ClashServer
SetClashServer(server ClashServer)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
}
type routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
}
type Rule interface {
@@ -54,3 +77,7 @@ type DNSRule interface {
Rule
DisableCache() bool
}
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

View File

@@ -1,12 +1,6 @@
package adapter
import "io"
type Starter interface {
Start() error
}
type Service interface {
Starter
io.Closer
Start() error
Close() error
}

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

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

27
adapter/v2ray.go Normal file
View File

@@ -0,0 +1,27 @@
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 {
Network() []string
Serve(listener net.Listener) error
ServePacket(listener net.PacketConn) error
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)
}

163
box.go
View File

@@ -2,12 +2,16 @@ package box
import (
"context"
"fmt"
"io"
"os"
"runtime/debug"
"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"
@@ -29,37 +33,51 @@ type Box struct {
logger log.ContextLogger
logFile *os.File
clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
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)
var needClashAPI bool
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true
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
}
}
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
}
logWriter = logFile
}
logFormatter := log.Formatter{
BaseTime: createdAt,
@@ -69,10 +87,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)
@@ -87,11 +105,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")
@@ -111,6 +130,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, "]")
@@ -135,7 +155,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
}
outbounds = append(outbounds, out)
}
err = router.Initialize(outbounds, func() adapter.Outbound {
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
@@ -146,12 +166,20 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
}
var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
router.SetTrafficController(clashServer)
router.SetClashServer(clashServer)
}
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
router.SetV2RayServer(v2rayServer)
}
return &Box{
router: router,
@@ -159,14 +187,58 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.NewLogger(""),
logger: logFactory.Logger(),
logFile: logFile,
clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}),
}, nil
}
func (s *Box) Start() error {
err := s.start()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
}
return err
}
func (s *Box) start() error {
if s.clashServer != nil {
err := s.clashServer.Start()
if err != nil {
return E.Cause(err, "start clash api server")
}
}
if s.v2rayServer != nil {
err := s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
for i, out := range s.outbounds {
if starter, isStarter := out.(common.Starter); isStarter {
err := starter.Start()
if err != nil {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
}
}
}
err := s.router.Start()
if err != nil {
return err
@@ -174,18 +246,16 @@ func (s *Box) Start() error {
for i, in := range s.inbounds {
err = in.Start()
if err != nil {
for g := 0; g < i; g++ {
s.inbounds[g].Close()
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
return err
}
}
if s.clashServer != nil {
err = s.clashServer.Start()
if err != nil {
return E.Cause(err, "start clash api server")
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
@@ -197,16 +267,45 @@ func (s *Box) Close() error {
default:
close(s.done)
}
for _, in := range s.inbounds {
in.Close()
var errors error
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, "]")
})
}
for _, out := range s.outbounds {
common.Close(out)
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, "]")
})
}
return common.Close(
s.router,
s.logFactory,
s.clashServer,
common.PtrOrNil(s.logFile),
)
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
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 err := common.Close(s.clashServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close clash api server")
})
}
if err := common.Close(s.v2rayServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close v2ray api server")
})
}
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 {
return s.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,113 @@
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()
}
}
func buildAndroid() {
build_shared.FindSDK()
args := []string{
"bind",
"-v",
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
)
} else {
args = append(args, "-tags", "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, "-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_utls,with_clash_api,with_conntrack",
)
} else {
args = append(args, "-tags", "with_gvisor,with_utls,with_clash_api,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,218 @@
package main
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile() (string, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", fmt.Errorf("GOENV=off")
}
return file, nil
}
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
if dir == "" {
return "", fmt.Errorf("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
func GetRuntimeEnv(key string) (string, error) {
file, err := envFile()
if err != nil {
return "", err
}
if file == "" {
return "", fmt.Errorf("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
}
}
return runtimeEnv, nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN() string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
}
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func main() {
pwd, err := os.Getwd()
if err != nil {
fmt.Println("Can not get current working directory.")
os.Exit(1)
}
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := ""
if runtime.GOOS == "windows" {
suffix = ".exe"
}
protoc := "protoc"
if linkPath, err := os.Readlink(protoc); err == nil {
protoc = linkPath
}
protoFilesMap := make(map[string][]string)
walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err
}
if info.IsDir() {
return nil
}
dir := filepath.Dir(path)
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".proto") &&
filename != "typed_message.proto" &&
filename != "descriptor.proto" {
protoFilesMap[dir] = append(protoFilesMap[dir], path)
}
return nil
})
if walkErr != nil {
fmt.Println(walkErr)
os.Exit(1)
}
for _, files := range protoFilesMap {
for _, relProtoFile := range files {
args := []string{
"-I", ".",
"--go_out", pwd,
"--go_opt", "paths=source_relative",
"--go-grpc_out", pwd,
"--go-grpc_opt", "paths=source_relative",
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix),
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix),
}
args = append(args, relProtoFile)
cmd := exec.Command(protoc, args...)
cmd.Env = append(cmd.Env, os.Environ()...)
output, cmdErr := cmd.CombinedOutput()
if len(output) > 0 {
fmt.Println(string(output))
}
if cmdErr != nil {
fmt.Println(cmdErr)
os.Exit(1)
}
}
}
normalizeWalkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err
}
if info.IsDir() {
return nil
}
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".pb.go") &&
path != "config.pb.go" {
if err := NormalizeGeneratedProtoFile(path); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
})
if normalizeWalkErr != nil {
fmt.Println(normalizeWalkErr)
os.Exit(1)
}
}
func NormalizeGeneratedProtoFile(path string) error {
fd, err := os.OpenFile(path, os.O_RDWR, 0o644)
if err != nil {
return err
}
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
return err
}
out := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(fd)
valid := false
for scanner.Scan() {
if !valid && !strings.HasPrefix(scanner.Text(), "package ") {
continue
}
valid = true
out.Write(scanner.Bytes())
out.Write([]byte("\n"))
}
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
return err
}
err = fd.Truncate(0)
if err != nil {
return err
}
_, err = io.Copy(fd, bytes.NewReader(out.Bytes()))
if err != nil {
return err
}
return nil
}

View File

@@ -2,12 +2,9 @@ package main
import (
"context"
"os"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/spf13/cobra"
)
@@ -15,24 +12,29 @@ import (
var commandCheck = &cobra.Command{
Use: "check",
Short: "Check configuration",
Run: checkConfiguration,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := check()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
func checkConfiguration(cmd *cobra.Command, args []string) {
configContent, err := os.ReadFile(configPath)
func init() {
mainCommand.AddCommand(commandCheck)
}
func check() error {
options, err := readConfig()
if err != nil {
log.Fatal("read config: ", err)
}
var options option.Options
err = json.Unmarshal(configContent, &options)
if err != nil {
log.Fatal("decode config: ", err)
return err
}
ctx, cancel := context.WithCancel(context.Background())
_, err = box.New(ctx, options)
if err != nil {
log.Fatal("create service: ", err)
instance, err := box.New(ctx, options, nil)
if err == nil {
instance.Close()
}
cancel()
return err
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
@@ -17,47 +18,54 @@ var commandFormatFlagWrite bool
var commandFormat = &cobra.Command{
Use: "format",
Short: "Format configuration",
Run: formatConfiguration,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := format()
if err != nil {
log.Fatal(err)
}
},
Args: cobra.NoArgs,
}
func init() {
commandFormat.Flags().BoolVarP(&commandFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
mainCommand.AddCommand(commandFormat)
}
func formatConfiguration(cmd *cobra.Command, args []string) {
func format() error {
configContent, err := os.ReadFile(configPath)
if err != nil {
log.Fatal("read config: ", err)
return E.Cause(err, "read config")
}
var options option.Options
err = json.Unmarshal(configContent, &options)
err = options.UnmarshalJSON(configContent)
if err != nil {
log.Fatal("decode config: ", err)
return E.Cause(err, "decode config")
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
log.Fatal("encode config: ", err)
return E.Cause(err, "encode config")
}
if !commandFormatFlagWrite {
os.Stdout.WriteString(buffer.String() + "\n")
return
return nil
}
if bytes.Equal(configContent, buffer.Bytes()) {
return
return nil
}
output, err := os.Create(configPath)
if err != nil {
log.Fatal("open output: ", err)
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
log.Fatal("write output: ", err)
return E.Cause(err, "write output")
}
outputPath, _ := filepath.Abs(configPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

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

@@ -2,16 +2,15 @@ package main
import (
"context"
"net/http"
"io"
"os"
"os/signal"
runtimeDebug "runtime/debug"
"syscall"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
@@ -20,25 +19,43 @@ import (
var commandRun = &cobra.Command{
Use: "run",
Short: "Run service",
Run: run,
Run: func(cmd *cobra.Command, args []string) {
err := run()
if err != nil {
log.Fatal(err)
}
},
}
func run(cmd *cobra.Command, args []string) {
err := run0()
if err != nil {
log.Fatal(err)
func init() {
mainCommand.AddCommand(commandRun)
}
func readConfig() (option.Options, error) {
var (
configContent []byte
err error
)
if configPath == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(configPath)
}
}
func run0() error {
configContent, err := os.ReadFile(configPath)
if err != nil {
return E.Cause(err, "read config")
return option.Options{}, E.Cause(err, "read config")
}
var options option.Options
err = json.Unmarshal(configContent, &options)
err = options.UnmarshalJSON(configContent)
if err != nil {
return E.Cause(err, "decode config")
return option.Options{}, E.Cause(err, "decode config")
}
return options, nil
}
func create() (*box.Box, context.CancelFunc, error) {
options, err := readConfig()
if err != nil {
return nil, nil, err
}
if disableColor {
if options.Log == nil {
@@ -47,26 +64,58 @@ func run0() 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 E.Cause(err, "create service")
return nil, nil, E.Cause(err, "create service")
}
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer func() {
signal.Stop(osSignals)
close(osSignals)
}()
go func() {
_, loaded := <-osSignals
if loaded {
cancel()
}
}()
err = instance.Start()
if err != nil {
cancel()
return E.Cause(err, "start service")
return nil, nil, E.Cause(err, "start service")
}
if debug.Enabled {
http.HandleFunc("/debug/close", func(writer http.ResponseWriter, request *http.Request) {
return instance, cancel, nil
}
func run() error {
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for {
instance, cancel, err := create()
if err != nil {
return err
}
runtimeDebug.FreeOSMemory()
for {
osSignal := <-osSignals
if osSignal == syscall.SIGHUP {
err = check()
if err != nil {
log.Error(E.Cause(err, "reload service"))
continue
}
}
cancel()
instance.Close()
})
if osSignal != syscall.SIGHUP {
return nil
}
break
}
}
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
<-osSignals
cancel()
instance.Close()
return nil
}

View File

@@ -3,9 +3,9 @@ package main
import (
"os"
"runtime"
"runtime/debug"
C "github.com/sagernet/sing-box/constant"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
@@ -17,11 +17,48 @@ var commandVersion = &cobra.Command{
Args: cobra.NoArgs,
}
func printVersion(cmd *cobra.Command, args []string) {
os.Stderr.WriteString(F.ToString("sing-box version ", C.Version, " (", runtime.Version(), ", ", runtime.GOOS, "/", runtime.GOARCH, ", CGO "))
if C.CGO_ENABLED {
os.Stderr.WriteString("enabled)\n")
} else {
os.Stderr.WriteString("disabled)\n")
}
var nameOnly bool
func init() {
commandVersion.Flags().BoolVarP(&nameOnly, "name", "n", false, "print version name only")
mainCommand.AddCommand(commandVersion)
}
func printVersion(cmd *cobra.Command, args []string) {
if nameOnly {
os.Stdout.WriteString(C.Version + "\n")
return
}
version := "sing-box version " + C.Version + "\n\n"
version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n"
var tags string
var revision string
debugInfo, loaded := debug.ReadBuildInfo()
if loaded {
for _, setting := range debugInfo.Settings {
switch setting.Key {
case "-tags":
tags = setting.Value
case "vcs.revision":
revision = setting.Value
}
}
}
if tags != "" {
version += "Tags: " + tags + "\n"
}
if revision != "" {
version += "Revision: " + revision + "\n"
}
if C.CGO_ENABLED {
version += "CGO: enabled\n"
} else {
version += "CGO: disabled\n"
}
os.Stdout.WriteString(version)
}

View File

@@ -3,6 +3,7 @@ package main
import (
"os"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
@@ -23,11 +24,6 @@ func init() {
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
mainCommand.AddCommand(commandRun)
mainCommand.AddCommand(commandCheck)
mainCommand.AddCommand(commandFormat)
mainCommand.AddCommand(commandVersion)
}
func main() {

View File

@@ -0,0 +1,62 @@
package baderror
import (
"context"
"io"
"net"
"strings"
E "github.com/sagernet/sing/common/exceptions"
)
func Contains(err error, msgList ...string) bool {
for _, msg := range msgList {
if strings.Contains(err.Error(), msg) {
return true
}
}
return false
}
func WrapH2(err error) error {
if err == nil {
return nil
}
err = E.Unwrap(err)
if err == io.ErrUnexpectedEOF {
return io.EOF
}
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
return net.ErrClosed
}
return err
}
func WrapGRPC(err error) error {
// grpc uses stupid internal error types
if err == nil {
return nil
}
if Contains(err, "EOF") {
return io.EOF
}
if Contains(err, "Canceled") {
return context.Canceled
}
if Contains(err,
"the client connection is closing",
"server closed the stream without sending trailers") {
return net.ErrClosed
}
return err
}
func WrapQUIC(err error) error {
if err == nil {
return nil
}
if Contains(err, "canceled with error code 0") {
return net.ErrClosed
}
return err
}

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

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

View File

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

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

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

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

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

19
common/debugio/print.go Normal file
View File

@@ -0,0 +1,19 @@
package debugio
import (
"fmt"
"reflect"
"github.com/sagernet/sing/common"
)
func PrintUpstream(obj any) {
for obj != nil {
fmt.Println(reflect.TypeOf(obj))
if u, ok := obj.(common.WithUpstream); !ok {
break
} else {
obj = u.Upstream()
}
}
}

View File

@@ -0,0 +1,51 @@
package conntrack
import (
"net"
"runtime/debug"
"github.com/sagernet/sing/common/x/list"
)
type Conn struct {
net.Conn
element *list.Element[*ConnEntry]
}
func NewConn(conn net.Conn) *Conn {
entry := &ConnEntry{
Conn: conn,
Stack: debug.Stack(),
}
connAccess.Lock()
element := openConnection.PushBack(entry)
connAccess.Unlock()
return &Conn{
Conn: conn,
element: element,
}
}
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,51 @@
package conntrack
import (
"net"
"runtime/debug"
"github.com/sagernet/sing/common/x/list"
)
type PacketConn struct {
net.PacketConn
element *list.Element[*ConnEntry]
}
func NewPacketConn(conn net.PacketConn) *PacketConn {
entry := &ConnEntry{
Conn: conn,
Stack: debug.Stack(),
}
connAccess.Lock()
element := openConnection.PushBack(entry)
connAccess.Unlock()
return &PacketConn{
PacketConn: conn,
element: element,
}
}
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,43 @@
package conntrack
import (
"io"
"sync"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/x/list"
)
var (
connAccess sync.RWMutex
openConnection list.List[*ConnEntry]
)
type ConnEntry struct {
Conn io.Closer
Stack []byte
}
func Count() int {
return openConnection.Len()
}
func List() []*ConnEntry {
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]*ConnEntry, 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.Conn)
element.Value = nil
}
openConnection = list.List[*ConnEntry]{}
}

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,14 +6,15 @@ 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"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/database64128/tfo-go"
"github.com/sagernet/tfo-go"
)
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
@@ -52,8 +53,13 @@ var warnTFOOnUnsupportedPlatform = warning.New(
)
type DefaultDialer struct {
tfo.Dialer
net.ListenConfig
dialer4 tfo.Dialer
dialer6 tfo.Dialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
}
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
@@ -61,25 +67,15 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
var listener net.ListenConfig
if options.BindInterface != "" {
warnBindInterfaceOnUnsupportedPlatform.Check()
bindFunc := control.BindToInterface(router.InterfaceBindManager(), options.BindInterface)
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() {
if C.IsWindows {
bindFunc := control.BindToInterfaceIndexFunc(func() int {
return router.InterfaceMonitor().DefaultInterfaceIndex()
})
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
return router.InterfaceMonitor().DefaultInterfaceName()
})
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface())
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
@@ -108,17 +104,86 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
}
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener}
var udpFragment bool
if options.UDPFragment != nil {
udpFragment = *options.UDPFragment
} else {
udpFragment = options.UDPFragmentDefault
}
if !udpFragment {
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
}
var (
dialer4 = dialer
udpDialer4 = dialer
udpAddr4 string
)
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build()
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
}
var (
dialer6 = dialer
udpDialer6 = dialer
udpAddr6 string
)
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build()
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
return &DefaultDialer{
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
}
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
return d.Dialer.DialContext(ctx, network, address.Unwrap().String())
if !address.IsValid() {
return nil, E.New("invalid address")
}
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
return d.udpDialer4.DialContext(ctx, network, address.String())
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
}
}
if !address.IsIPv6() {
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.ListenConfig.ListenPacket(ctx, N.NetworkUDP, "")
if !destination.IsIPv6() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
} else {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
}
}
func (d *DefaultDialer) Upstream() any {
return &d.Dialer
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewConn(conn), nil
}
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewPacketConn(conn), nil
}

View File

@@ -10,15 +10,12 @@ import (
)
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
var dialer N.Dialer
if options.Detour == "" {
return NewDefault(router, options)
dialer = NewDefault(router, options)
} else {
return NewDetour(router, options.Detour)
dialer = NewDetour(router, options.Detour)
}
}
func NewOutbound(router adapter.Router, options option.OutboundDialerOptions) N.Dialer {
dialer := New(router, options.DialerOptions)
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))

View File

@@ -51,7 +51,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
}
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsFqdn() || destination.Fqdn == "" {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx, metadata := adapter.AppendContext(ctx)

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

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

View File

@@ -4,7 +4,6 @@ import (
"net/netip"
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

@@ -20,13 +20,11 @@ func Write(writer io.Writer, domains map[string][]Item) error {
for _, code := range keys {
index[code] = content.Len()
for _, domain := range domains[code] {
err := rw.WriteByte(content, domain.Type)
content.WriteByte(domain.Type)
err := rw.WriteVString(content, domain.Value)
if err != nil {
return err
}
if err = rw.WriteVString(content, domain.Value); err != nil {
return err
}
}
}

128
common/json/comment.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -4,18 +4,29 @@ import (
"context"
"net/netip"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
)
type Searcher interface {
FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error)
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error)
}
var ErrNotFound = E.New("process not found")
type Config struct {
Logger log.ContextLogger
PackageManager tun.PackageManager
}
type Info struct {
ProcessPath string
PackageName string
User string
UserId int32
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return findProcessInfo(searcher, ctx, network, source, destination)
}

View File

@@ -2,170 +2,37 @@ package process
import (
"context"
"encoding/xml"
"io"
"net/netip"
"os"
"strconv"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/fsnotify/fsnotify"
"github.com/sagernet/sing-tun"
)
var _ Searcher = (*androidSearcher)(nil)
type androidSearcher struct {
logger log.ContextLogger
watcher *fsnotify.Watcher
userMap map[string]int32
packageMap map[int32]string
sharedUserMap map[int32]string
packageManager tun.PackageManager
}
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
return &androidSearcher{logger: logger}, nil
func NewSearcher(config Config) (Searcher, error) {
return &androidSearcher{config.PackageManager}, nil
}
func (s *androidSearcher) Start() error {
err := s.updatePackages()
if err != nil {
return E.Cause(err, "read packages list")
}
err = s.startWatcher()
if err != nil {
s.logger.Warn("create fsnotify watcher: ", err)
}
return nil
}
func (s *androidSearcher) startWatcher() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
err = watcher.Add("/data/system/packages.xml")
if err != nil {
return err
}
s.watcher = watcher
go s.loopUpdate()
return nil
}
func (s *androidSearcher) loopUpdate() {
for {
select {
case _, ok := <-s.watcher.Events:
if !ok {
return
}
err := s.updatePackages()
if err != nil {
s.logger.Error(E.Cause(err, "update packages list"))
}
case err, ok := <-s.watcher.Errors:
if !ok {
return
}
s.logger.Error(E.Cause(err, "fsnotify error"))
}
}
}
func (s *androidSearcher) Close() error {
return common.Close(common.PtrOrNil(s.watcher))
}
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
_, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
_, uid, err := resolveSocketByNetlink(network, source, destination)
if err != nil {
return nil, err
}
if sharedUser, loaded := s.sharedUserMap[uid]; loaded {
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
return &Info{
UserId: uid,
PackageName: sharedUser,
UserId: int32(uid),
PackageName: sharedPackage,
}, nil
}
if packageName, loaded := s.packageMap[uid]; loaded {
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
return &Info{
UserId: uid,
UserId: int32(uid),
PackageName: packageName,
}, nil
}
return &Info{UserId: uid}, nil
}
func (s *androidSearcher) updatePackages() error {
userMap := make(map[string]int32)
packageMap := make(map[int32]string)
sharedUserMap := make(map[int32]string)
packagesData, err := os.Open("/data/system/packages.xml")
if err != nil {
return err
}
decoder := xml.NewDecoder(packagesData)
var token xml.Token
for {
token, err = decoder.Token()
if err == io.EOF {
break
} else if err != nil {
return err
}
element, isStart := token.(xml.StartElement)
if !isStart {
continue
}
switch element.Name.Local {
case "package":
var name string
var userID int64
for _, attr := range element.Attr {
switch attr.Name.Local {
case "name":
name = attr.Value
case "userId", "sharedUserId":
userID, err = strconv.ParseInt(attr.Value, 10, 32)
if err != nil {
return err
}
}
}
if userID == 0 && name == "" {
continue
}
userMap[name] = int32(userID)
packageMap[int32(userID)] = name
case "shared-user":
var name string
var userID int64
for _, attr := range element.Attr {
switch attr.Name.Local {
case "name":
name = attr.Value
case "userId":
userID, err = strconv.ParseInt(attr.Value, 10, 32)
if err != nil {
return err
}
packageMap[int32(userID)] = name
}
}
if userID == 0 && name == "" {
continue
}
sharedUserMap[int32(userID)] = name
}
}
s.logger.Info("updated packages list: ", len(packageMap), " packages, ", len(sharedUserMap), " shared users")
s.userMap = userMap
s.packageMap = packageMap
s.sharedUserMap = sharedUserMap
return nil
return &Info{UserId: int32(uid)}, nil
}

View File

@@ -5,10 +5,11 @@ import (
"encoding/binary"
"net/netip"
"os"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/sagernet/sing-box/log"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/unix"
@@ -18,18 +19,34 @@ var _ Searcher = (*darwinSearcher)(nil)
type darwinSearcher struct{}
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
func NewSearcher(_ Config) (Searcher, error) {
return &darwinSearcher{}, nil
}
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
processName, err := findProcessName(network, srcIP, srcPort)
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
if err != nil {
return nil, err
}
return &Info{ProcessPath: processName, UserId: -1}, nil
}
var structSize = func() int {
value, _ := syscall.Sysctl("kern.osrelease")
major, _, _ := strings.Cut(value, ".")
n, _ := strconv.ParseInt(major, 10, 64)
switch true {
case n >= 22:
return 408
default:
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
return 384
}
}()
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
var spath string
switch network {
@@ -54,7 +71,7 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := 384
itemSize := structSize
if network == N.NetworkTCP {
// rup8(sizeof(xtcpcb_n))
itemSize += 208

View File

@@ -15,12 +15,12 @@ type linuxSearcher struct {
logger log.ContextLogger
}
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
return &linuxSearcher{logger}, nil
func NewSearcher(config Config) (Searcher, error) {
return &linuxSearcher{config.Logger}, nil
}
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
inode, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
inode, uid, err := resolveSocketByNetlink(network, source, destination)
if err != nil {
return nil, err
}
@@ -29,7 +29,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, src
s.logger.DebugContext(ctx, "find process path: ", err)
}
return &Info{
UserId: uid,
UserId: int32(uid),
ProcessPath: processPath,
}, nil
}

View File

@@ -37,19 +37,9 @@ const (
pathProc = "/proc"
)
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (inode int32, uid int32, err error) {
for attempts := 0; attempts < 3; attempts++ {
inode, uid, err = resolveSocketByNetlink0(network, ip, srcPort)
if err == nil {
return
}
}
return
}
func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode int32, uid int32, err error) {
var family byte
var protocol byte
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
var family uint8
var protocol uint8
switch network {
case N.NetworkTCP:
@@ -60,13 +50,13 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
return 0, 0, os.ErrInvalid
}
if ip.Is4() {
if source.Addr().Is4() {
family = syscall.AF_INET
} else {
family = syscall.AF_INET6
}
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
req := packSocketDiagRequest(family, protocol, source)
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
@@ -77,16 +67,18 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
if err = syscall.Connect(socket, &syscall.SockaddrNetlink{
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
}); err != nil {
return 0, 0, err
})
if err != nil {
return
}
if _, err = syscall.Write(socket, req); err != nil {
_, err = syscall.Write(socket, req)
if err != nil {
return 0, 0, E.Cause(err, "write netlink request")
}
@@ -115,15 +107,12 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode
}
inode, uid = unpackSocketDiagResponse(&messages[0])
if inode < 0 || uid < 0 {
return 0, 0, E.New("invalid inode(", inode, ") or uid(", uid, ")")
}
return
}
func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort uint16) []byte {
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
s := make([]byte, 16)
copy(s, source.AsSlice())
copy(s, source.Addr().AsSlice())
buf := make([]byte, sizeOfSocketDiagRequest)
@@ -139,7 +128,7 @@ func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], sourcePort)
binary.BigEndian.PutUint16(buf[24:26], source.Port())
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
@@ -151,20 +140,20 @@ func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
return
}
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
@@ -182,7 +171,7 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
if err != nil {
return "", err
}
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
if info.Sys().(*syscall.Stat_t).Uid != uid {
continue
}

View File

@@ -4,10 +4,8 @@ package process
import (
"os"
"github.com/sagernet/sing-box/log"
)
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
func NewSearcher(_ Config) (Searcher, error) {
return nil, os.ErrInvalid
}

View File

@@ -8,7 +8,6 @@ import (
"syscall"
"unsafe"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -19,7 +18,7 @@ var _ Searcher = (*windowsSearcher)(nil)
type windowsSearcher struct{}
func NewSearcher(logger log.ContextLogger) (Searcher, error) {
func NewSearcher(_ Config) (Searcher, error) {
err := initWin32API()
if err != nil {
return nil, E.Cause(err, "init win32 api")
@@ -64,8 +63,8 @@ func initWin32API() error {
return nil
}
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
processName, err := findProcessName(network, srcIP, srcPort)
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
if err != nil {
return nil, err
}

View File

@@ -1,4 +1,4 @@
//go:build cgo && linux && !android
//go:build linux && !android
package process
@@ -10,8 +10,8 @@ import (
F "github.com/sagernet/sing/common/format"
)
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, srcIP, srcPort)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}

View File

@@ -1,4 +1,4 @@
//go:build !(cgo && linux && !android)
//go:build !linux || android
package process
@@ -7,6 +7,6 @@ import (
"net/netip"
)
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, srcIP netip.Addr, srcPort int) (*Info, error) {
return searcher.FindProcessInfo(ctx, network, srcIP, srcPort)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return searcher.FindProcessInfo(ctx, network, source, destination)
}

View File

@@ -0,0 +1,50 @@
package proxyproto
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/pires/go-proxyproto"
)
var _ N.Dialer = (*Dialer)(nil)
type Dialer struct {
N.Dialer
}
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
conn, err := d.Dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
var source M.Socksaddr
metadata := adapter.ContextFrom(ctx)
if metadata != nil {
source = metadata.Source
}
if !source.IsValid() {
source = M.SocksaddrFromNet(conn.LocalAddr())
}
if destination.Addr.Is6() {
source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
}
h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr())
_, err = h.WriteTo(conn)
if err != nil {
conn.Close()
return nil, E.Cause(err, "write proxy protocol header")
}
return conn, nil
default:
return d.Dialer.DialContext(ctx, network, destination)
}
}

View File

@@ -0,0 +1,62 @@
package proxyproto
import (
std_bufio "bufio"
"net"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
"github.com/pires/go-proxyproto"
)
type Listener struct {
net.Listener
AcceptNoHeader bool
}
func (l *Listener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader)
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
return nil, &Error{err}
}
if bufReader.Buffered() > 0 {
cache := buf.NewSize(bufReader.Buffered())
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
if err != nil {
return nil, &Error{err}
}
conn = bufio.NewCachedConn(conn, cache)
}
if header != nil {
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
}}, nil
}
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

@@ -0,0 +1,64 @@
package redir
import (
"net"
"net/netip"
"syscall"
"unsafe"
M "github.com/sagernet/sing/common/metadata"
)
const (
PF_OUT = 0x2
DIOCNATLOOK = 0xc0544417
)
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY)
if err != nil {
return netip.AddrPort{}, err
}
defer syscall.Close(fd)
nl := struct {
saddr, daddr, rsaddr, rdaddr [16]byte
sxport, dxport, rsxport, rdxport [4]byte
af, proto, protoVariant, direction uint8
}{
af: syscall.AF_INET,
proto: syscall.IPPROTO_TCP,
direction: PF_OUT,
}
la := conn.LocalAddr().(*net.TCPAddr)
ra := conn.RemoteAddr().(*net.TCPAddr)
raIP, laIP := ra.IP, la.IP
raPort, laPort := ra.Port, la.Port
switch {
case raIP.To4() != nil:
copy(nl.saddr[:net.IPv4len], raIP.To4())
copy(nl.daddr[:net.IPv4len], laIP.To4())
nl.af = syscall.AF_INET
default:
copy(nl.saddr[:], raIP.To16())
copy(nl.daddr[:], laIP.To16())
nl.af = syscall.AF_INET6
}
nl.sxport[0], nl.sxport[1] = byte(raPort>>8), byte(raPort)
nl.dxport[0], nl.dxport[1] = byte(laPort>>8), byte(laPort)
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {
return netip.AddrPort{}, errno
}
var ip net.IP
switch nl.af {
case syscall.AF_INET:
ip = make(net.IP, net.IPv4len)
copy(ip, nl.rdaddr[:net.IPv4len])
case syscall.AF_INET6:
ip = make(net.IP, net.IPv6len)
copy(ip, nl.rdaddr[:])
}
port := uint16(nl.rdxport[0])<<8 | uint16(nl.rdxport[1])
destination = netip.AddrPortFrom(M.AddrFromIP(ip), port)
return
}

View File

@@ -1,37 +1,40 @@
package redir
import (
"encoding/binary"
"net"
"net/netip"
"os"
"syscall"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
M "github.com/sagernet/sing/common/metadata"
)
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
rawConn, err := conn.(syscall.Conn).SyscallConn()
if err != nil {
return
syscallConn, ok := common.Cast[syscall.Conn](conn)
if !ok {
return netip.AddrPort{}, os.ErrInvalid
}
var rawFd uintptr
err = rawConn.Control(func(fd uintptr) {
rawFd = fd
err = control.Conn(syscallConn, func(fd uintptr) error {
const SO_ORIGINAL_DST = 80
if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
raw, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return err
}
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3]))
} else {
raw, err := syscall.GetsockoptIPv6MTUInfo(int(fd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)
if err != nil {
return err
}
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
})
if err != nil {
return
}
const SO_ORIGINAL_DST = 80
if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
raw, err := syscall.GetsockoptIPv6Mreq(int(rawFd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil
} else {
raw, err := syscall.GetsockoptIPv6MTUInfo(int(rawFd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port), nil
}
return
}

View File

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

View File

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

View File

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

View File

@@ -1,21 +0,0 @@
package settings
import (
"os"
"os/exec"
)
func runCommand(name string, args ...string) error {
command := exec.Command(name, args...)
command.Env = os.Environ()
command.Stdin = os.Stdin
command.Stdout = os.Stderr
command.Stderr = os.Stderr
return command.Run()
}
func readCommand(name string, args ...string) ([]byte, error) {
command := exec.Command(name, args...)
command.Env = os.Environ()
return command.CombinedOutput()
}

View File

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

View File

@@ -1,10 +1,12 @@
package settings
import (
"net/netip"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/x/list"
@@ -18,8 +20,8 @@ type systemProxy struct {
isMixed bool
}
func (p *systemProxy) update() error {
newInterfaceName := p.monitor.DefaultInterfaceName()
func (p *systemProxy) update(event int) error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return nil
}
@@ -32,13 +34,13 @@ func (p *systemProxy) update() error {
return err
}
if p.isMixed {
err = runCommand("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
err = common.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = runCommand("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
err = common.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = runCommand("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
err = common.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return err
}
@@ -49,19 +51,19 @@ func (p *systemProxy) unset() error {
return err
}
if p.isMixed {
err = runCommand("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off")
err = common.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = runCommand("networksetup", "-setwebproxystate", interfaceDisplayName, "off")
err = common.Exec("networksetup", "-setwebproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
err = runCommand("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off")
err = common.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
}
return err
}
func getInterfaceDisplayName(name string) (string, error) {
content, err := readCommand("networksetup", "-listallhardwareports")
content, err := common.Exec("networksetup", "-listallhardwareports").Read()
if err != nil {
return "", err
}
@@ -86,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
port: port,
isMixed: isMixed,
}
err := proxy.update()
err := proxy.update(tun.EventInterfaceUpdate)
if err != nil {
return nil, err
}

View File

@@ -27,9 +27,9 @@ func init() {
func runAsUser(name string, args ...string) error {
if os.Getuid() != 0 {
return runCommand(name, args...)
return common.Exec(name, args...).Attach().Run()
} else if sudoUser != "" {
return runCommand("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " ")))
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

86
common/tls/acme.go Normal file
View File

@@ -0,0 +1,86 @@
//go:build with_acme
package tls
import (
"context"
"crypto/tls"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/acme"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type acmeWrapper struct {
ctx context.Context
cfg *certmagic.Config
domain []string
}
func (w *acmeWrapper) Start() error {
return w.cfg.ManageSync(w.ctx, w.domain)
}
func (w *acmeWrapper) Close() error {
w.cfg.Unmanage(w.domain)
return nil
}
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
var acmeServer string
switch options.Provider {
case "", "letsencrypt":
acmeServer = certmagic.LetsEncryptProductionCA
case "zerossl":
acmeServer = certmagic.ZeroSSLProductionCA
default:
if !strings.HasPrefix(options.Provider, "https://") {
return nil, nil, E.New("unsupported acme provider: " + options.Provider)
}
acmeServer = options.Provider
}
var storage certmagic.Storage
if options.DataDirectory != "" {
storage = &certmagic.FileStorage{
Path: options.DataDirectory,
}
} else {
storage = certmagic.Default.Storage
}
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,
Email: options.Email,
Agreed: true,
DisableHTTPChallenge: options.DisableHTTPChallenge,
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: config.Logger,
}
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
}
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
config = certmagic.New(certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
}), *config)
return config.TLSConfig(), &acmeWrapper{ctx, config, options.Domain}, nil
}

16
common/tls/acme_stub.go Normal file
View File

@@ -0,0 +1,16 @@
//go:build !with_acme
package tls
import (
"context"
"crypto/tls"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
}

70
common/tls/client.go Normal file
View File

@@ -0,0 +1,70 @@
package tls
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
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) {
if !options.Enabled {
return dialer, nil
}
config, err := NewClient(router, serverAddress, options)
if err != nil {
return nil, err
}
return NewDialer(dialer, config), nil
}
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
if !options.Enabled {
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(router, serverAddress, options)
} else if options.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(router, serverAddress, options)
}
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
return aTLS.ClientHandshake(ctx, conn, config)
}
type Dialer struct {
dialer N.Dialer
config Config
}
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
return &Dialer{dialer, config}
}
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if network != N.NetworkTCP {
return nil, os.ErrInvalid
}
conn, err := d.dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
return ClientHandshake(ctx, conn, d.config)
}
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}

12
common/tls/common.go Normal file
View File

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

36
common/tls/config.go Normal file
View File

@@ -0,0 +1,36 @@
package tls
import (
"crypto/tls"
E "github.com/sagernet/sing/common/exceptions"
aTLS "github.com/sagernet/sing/common/tls"
)
type (
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
)
func ParseTLSVersion(version string) (uint16, error) {
switch version {
case "1.0":
return tls.VersionTLS10, nil
case "1.1":
return tls.VersionTLS11, nil
case "1.2":
return tls.VersionTLS12, nil
case "1.3":
return tls.VersionTLS13, nil
default:
return 0, E.New("unknown tls version:", version)
}
}

226
common/tls/ech_client.go Normal file
View File

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

13
common/tls/ech_stub.go Normal file
View File

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

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

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

View File

@@ -0,0 +1,230 @@
//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 []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")
}
shortID, err := hex.DecodeString(options.Reality.ShortID)
if err != nil {
return nil, E.Cause(err, "decode short_id")
}
if len(shortID) != 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, shortID := range options.Reality.ShortID {
var shortIDBytesArray [8]byte
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortID)
}
if decodedLen != 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortID)
}
tlsConfig.ShortIds[shortIDBytesArray] = 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`)
}

29
common/tls/server.go Normal file
View File

@@ -0,0 +1,29 @@
package tls
import (
"context"
"net"
"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, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
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) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
return aTLS.ServerHandshake(ctx, conn, config)
}

View File

@@ -1,30 +1,50 @@
package dialer
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/netip"
"os"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type TLSDialer struct {
dialer N.Dialer
type STDClientConfig struct {
config *tls.Config
}
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
}
func (s *STDClientConfig) ServerName() string {
return s.config.ServerName
}
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, s.config), nil
}
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
@@ -33,11 +53,12 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt
serverName = serverAddress
}
}
if serverName == "" && options.Insecure {
if serverName == "" && !options.Insecure {
return nil, E.New("missing server_name or insecure=true")
}
var tlsConfig tls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
@@ -63,14 +84,14 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt
tlsConfig.NextProtos = options.ALPN
}
if options.MinVersion != "" {
minVersion, err := option.ParseTLSVersion(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 := option.ParseTLSVersion(options.MaxVersion)
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
@@ -105,27 +126,5 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt
}
tlsConfig.RootCAs = certPool
}
return &TLSDialer{
dialer: dialer,
config: &tlsConfig,
}, nil
}
func (d *TLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if network != N.NetworkTCP {
return nil, os.ErrInvalid
}
conn, err := d.dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, d.config)
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
err = tlsConn.HandshakeContext(ctx)
return tlsConn, err
}
func (d *TLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
return &STDClientConfig{&tlsConfig}, nil
}

259
common/tls/std_server.go Normal file
View File

@@ -0,0 +1,259 @@
package tls
import (
"context"
"crypto/tls"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/fsnotify/fsnotify"
)
var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
config *tls.Config
logger log.Logger
acmeService adapter.Service
certificate []byte
key []byte
certificatePath string
keyPath string
watcher *fsnotify.Watcher
}
func (c *STDServerConfig) ServerName() string {
return c.config.ServerName
}
func (c *STDServerConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
}
func (c *STDServerConfig) NextProtos() []string {
return c.config.NextProtos
}
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
}
func (c *STDServerConfig) Config() (*STDConfig, error) {
return c.config, nil
}
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, c.config), nil
}
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
return tls.Server(conn, c.config), nil
}
func (c *STDServerConfig) Clone() Config {
return &STDServerConfig{
config: c.config.Clone(),
}
}
func (c *STDServerConfig) Start() error {
if c.acmeService != nil {
return c.acmeService.Start()
} else {
if c.certificatePath == "" && c.keyPath == "" {
return nil
}
err := c.startWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
}
return nil
}
}
func (c *STDServerConfig) startWatcher() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
if c.certificatePath != "" {
err = watcher.Add(c.certificatePath)
if err != nil {
return err
}
}
if c.keyPath != "" {
err = watcher.Add(c.keyPath)
if err != nil {
return err
}
}
c.watcher = watcher
go c.loopUpdate()
return nil
}
func (c *STDServerConfig) loopUpdate() {
for {
select {
case event, ok := <-c.watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write != fsnotify.Write {
continue
}
err := c.reloadKeyPair()
if err != nil {
c.logger.Error(E.Cause(err, "reload TLS key pair"))
}
case err, ok := <-c.watcher.Errors:
if !ok {
return
}
c.logger.Error(E.Cause(err, "fsnotify error"))
}
}
}
func (c *STDServerConfig) reloadKeyPair() error {
if c.certificatePath != "" {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath)
}
c.certificate = certificate
}
if c.keyPath != "" {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return E.Cause(err, "reload key from ", c.keyPath)
}
c.key = key
}
keyPair, err := tls.X509KeyPair(c.certificate, c.key)
if err != nil {
return E.Cause(err, "reload key pair")
}
c.config.Certificates = []tls.Certificate{keyPair}
c.logger.Info("reloaded TLS certificate")
return nil
}
func (c *STDServerConfig) Close() error {
if c.acmeService != nil {
return c.acmeService.Close()
}
if c.watcher != nil {
return c.watcher.Close()
}
return nil
}
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
var tlsConfig *tls.Config
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
//nolint:staticcheck
if err != nil {
return nil, err
}
if options.Insecure {
return nil, errInsecureUnused
}
} else {
tlsConfig = &tls.Config{}
}
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)
}
}
var certificate []byte
var key []byte
if acmeService == nil {
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if options.Key != "" {
key = []byte(options.Key)
} else if options.KeyPath != "" {
content, err := os.ReadFile(options.KeyPath)
if err != nil {
return nil, E.Cause(err, "read key")
}
key = content
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
}
} else {
if certificate == nil {
return nil, E.New("missing certificate")
} else if key == nil {
return nil, E.New("missing key")
}
keyPair, err := tls.X509KeyPair(certificate, key)
if err != nil {
return nil, E.Cause(err, "parse x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
return &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
}, nil
}

216
common/tls/utls_client.go Normal file
View File

@@ -0,0 +1,216 @@
//go:build with_utls
package tls
import (
"crypto/tls"
"crypto/x509"
"math/rand"
"net"
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
utls "github.com/sagernet/utls"
"golang.org/x/net/http2"
)
type UTLSClientConfig struct {
config *utls.Config
id utls.ClientHelloID
}
func (e *UTLSClientConfig) ServerName() string {
return e.config.ServerName
}
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (e *UTLSClientConfig) NextProtos() []string {
return e.config.NextProtos
}
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
nextProto = append(nextProto, "http/1.1")
}
e.config.NextProtos = nextProto
}
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS")
}
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, 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 {
*utls.UConn
}
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
} else if serverAddress != "" {
if _, err := netip.ParseAddr(serverName); err != nil {
serverName = serverAddress
}
}
if serverName == "" && !options.Insecure {
return nil, E.New("missing server_name or insecure=true")
}
var tlsConfig utls.Config
tlsConfig.Time = router.TimeFunc()
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
tlsConfig.InsecureSkipVerify = options.Insecure
} else if options.DisableSNI {
return nil, E.New("disable_sni is unsupported in uTLS")
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)
if err != nil {
return nil, E.Cause(err, "parse min_version")
}
tlsConfig.MinVersion = minVersion
}
if options.MaxVersion != "" {
maxVersion, err := ParseTLSVersion(options.MaxVersion)
if err != nil {
return nil, E.Cause(err, "parse max_version")
}
tlsConfig.MaxVersion = maxVersion
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
if options.Certificate != "" {
certificate = []byte(options.Certificate)
} else if options.CertificatePath != "" {
content, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate")
}
certificate = content
}
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", certificate)
}
tlsConfig.RootCAs = certPool
}
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)
}
}

17
common/tls/utls_stub.go Normal file
View File

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

View File

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

8
constant/dhcp.go Normal file
View File

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

View File

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

7
constant/err.go Normal file
View File

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

View File

@@ -20,7 +20,7 @@ const IsIos = goos.IsIos == 1
const IsJs = goos.IsJs == 1
const IsLinux = goos.IsLinux == 1
const IsLinux = goos.IsLinux == 1 || goos.IsAndroid == 1
const IsNacl = goos.IsNacl == 1

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