Compare commits

...

279 Commits

Author SHA1 Message Date
世界
fbb01555c8 documentation: Update changelog 2023-04-19 22:05:54 +08:00
世界
3c217fea63 Improve VLESS request 2023-04-19 22:00:05 +08:00
世界
a02c676d33 Update badtls 2023-04-19 22:00:05 +08:00
世界
c2ae261c8d Add deadline interface 2023-04-19 22:00:05 +08:00
世界
1fe69cc08c shadowsocks: Multi-user support for legacy AEAD inbound
Signed-off-by: wwqgtxx <wwqgtxx@gmail.com>
2023-04-19 22:00:05 +08:00
世界
dacb885930 Add headers option for HTTP outbound 2023-04-19 22:00:05 +08:00
世界
31682c313b URLTest improvements 2023-04-19 21:59:59 +08:00
世界
6fd73131cb Fix wireguard reconnect 2023-04-19 21:59:58 +08:00
世界
f3ff76abcc Use HTTPS URLTest source 2023-04-19 21:59:58 +08:00
H1JK
d9757bfaff Add BaseContext to http servers 2023-04-19 21:59:58 +08:00
H1JK
9d97ed6220 Fix gRPC service name escape 2023-04-19 21:59:58 +08:00
Hellojack
054211d091 Remove TLS requirement for gRPC client 2023-04-19 21:59:51 +08:00
世界
69b92810d6 clash-api: Add Clash.Meta APIs 2023-04-19 21:59:51 +08:00
世界
ea23267ced clash api: download clash-dashboard if external-ui directory is empty 2023-04-19 21:59:51 +08:00
世界
bbe2b1ab09 Add multi-peer support for wireguard outbound 2023-04-19 21:59:51 +08:00
世界
d3530a54fb Add fakeip support 2023-04-19 21:59:47 +08:00
世界
c238df9309 Add L3 routing support 2023-04-19 21:59:47 +08:00
世界
a223262678 Add dns reverse mapping 2023-04-19 21:59:47 +08:00
armv9
ec13965fd0 Fix HTTP sniffer 2023-04-19 21:58:58 +08:00
世界
ddf747006e Update to uuid v5 2023-04-19 21:58:58 +08:00
世界
4382093868 Prepare deadline interface 2023-04-19 21:58:58 +08:00
世界
a5322850b3 documentation: Update client notes 2023-04-19 21:58:58 +08:00
世界
407b08975c Remove legacy warnings 2023-04-19 21:58:58 +08:00
世界
c7067ff5e8 Fix default interface monitor for darwin 2023-04-19 21:58:58 +08:00
世界
9b2384b296 Add udpnat test 2023-04-19 21:43:13 +08:00
世界
b498a22972 Fix interface monitor for android 2023-04-19 21:42:40 +08:00
世界
20e9da5c67 Fix udp timeout 2023-04-19 21:42:10 +08:00
世界
ec8974673b Fix platform interface monitor & Fix system tun stack for ios 2023-04-19 21:40:03 +08:00
世界
5e6e7923e4 Fix shadowsocksr build 2023-04-17 18:06:43 +08:00
世界
de1b5971e1 Update documentation 2023-04-16 16:28:41 +08:00
世界
5c20d0b4d5 Update dependencies 2023-04-16 16:28:41 +08:00
世界
9df96ac7f1 Fix deadline usage on websocket conn 2023-04-16 16:28:40 +08:00
世界
87cd925144 Fix conntrack return pointer 2023-04-14 21:00:45 +08:00
世界
fecb796000 android: Remove Seq.Delete warning 2023-04-14 21:00:40 +08:00
世界
cfb6c804aa Print sniff result 2023-04-14 21:00:01 +08:00
世界
11c50c7558 Fix processing domain address in packet 2023-04-14 20:59:57 +08:00
世界
34cc7f176e Fix parsing query in http path 2023-04-14 20:59:16 +08:00
Xiaokang Wang (Shelikhoo)
b54da9c6af Fix '?' at end of WebSocket path get escaped
This fix align sing-box's behaviour with V2Ray when it comes to processing ? at the end of WebSocket's path.
2023-04-14 20:58:12 +08:00
世界
f44f86b832 Fix workflows 2023-04-14 20:57:08 +08:00
世界
4ebf40f582 Fix find process user 2023-04-14 20:56:58 +08:00
世界
53e4302143 Fix set HTTP TLS ALPN 2023-04-14 20:56:55 +08:00
世界
cf778eda4f Fix v2ray http transport server read request 2023-04-14 20:56:51 +08:00
世界
bb63429079 Update cancel context usage 2023-04-14 20:56:16 +08:00
世界
f7f9a7ae20 Fix write log to stderr 2023-04-14 20:55:57 +08:00
世界
8699412a4c platform: Add stderr redirect 2023-04-14 20:55:45 +08:00
世界
0d7aa19cd1 Fix write http status after response sent 2023-04-14 20:55:20 +08:00
世界
50a7295360 Replace usages of uber/atomic 2023-04-14 20:55:05 +08:00
世界
e57b6ae98d Update dependencies 2023-04-14 20:54:56 +08:00
世界
6843970536 Add loopback check 2023-04-08 09:13:50 +08:00
世界
62425ad3e4 Add close monitor 2023-04-08 08:10:03 +08:00
世界
e1e217854e Add start and close track message 2023-04-08 08:09:28 +08:00
世界
5bf177b021 platform: Fix build on windows 2023-04-07 21:10:16 +08:00
世界
72dbf2e2b4 documentation: Update changelog 2023-04-07 19:18:26 +08:00
世界
46c318c6fe Fix v2ray HTTP/1.1 transport compatibility 2023-04-07 18:20:07 +08:00
世界
05bb1b88c3 dns: Fix rewrite TTL 2023-04-07 16:19:34 +08:00
世界
5176ea9fe0 Update dependencies 2023-04-07 16:19:34 +08:00
世界
36d349acd2 dns: Fix calculate TTL 2023-04-07 13:12:16 +08:00
世界
4feee983b5 Update reality protocol 2023-04-06 19:05:05 +08:00
世界
9b12e3e389 Update client documentation 2023-04-06 12:51:26 +08:00
世界
afd3464216 Minor fixes 2023-04-05 21:41:06 +08:00
世界
8b64446274 platform: Fixes and improvements 2023-04-05 19:54:20 +08:00
世界
28aa4c4d1f Refactor log factory constructor 2023-04-03 20:24:13 +08:00
世界
0be3cdc8fb platform: Add http client 2023-04-03 15:12:44 +08:00
armv9
f8be484019 conntrack: Fix missing tracking for udp conn 2023-04-02 12:06:03 +08:00
世界
35f03f092d Improve UDP domain destination NAT 2023-04-02 12:05:59 +08:00
世界
c3d7401ead platform: Add check config func 2023-04-02 10:35:03 +08:00
世界
4db7eb9d9e documentation: Update changelog 2023-03-31 16:29:08 +08:00
世界
fd4efd6104 Fix dns transport read 2023-03-31 14:31:35 +08:00
世界
19a35ec6a4 Fix http2 transport close 2023-03-31 14:31:35 +08:00
世界
2012c0ca1e Update release scripts 2023-03-31 14:31:35 +08:00
世界
187421c754 Append time to session log 2023-03-31 14:31:35 +08:00
世界
b3fb86d415 Accept "any" outbound in dns rule 2023-03-31 14:31:35 +08:00
世界
88fafd4e30 Fix dns routing context 2023-03-31 09:14:04 +08:00
世界
8056932f9c Update documentation 2023-03-27 08:23:01 +08:00
世界
c8af003bfc Update dependencies 2023-03-27 08:22:56 +08:00
世界
4999441a85 Fix missing default host in v2ray http transport`s request 2023-03-27 08:20:59 +08:00
世界
09b001e795 Revert remove install shell 2023-03-27 08:20:55 +08:00
世界
3b3a251008 Update LICENSE 2023-03-27 08:20:51 +08:00
世界
2e4eb9aa39 Update dockerfile 2023-03-24 08:29:11 +08:00
世界
77fd284703 documentation: Update changelog 2023-03-24 08:04:36 +08:00
世界
0a4517f4b7 Update dependencies 2023-03-24 07:06:45 +08:00
世界
4395db3206 documentation: Update set_system_proxy usage 2023-03-23 21:27:50 +08:00
世界
dd5b0abc67 Fix slow open 2023-03-23 17:14:38 +08:00
世界
466800aa3a Fix wireguard mutex 2023-03-23 15:43:17 +08:00
世界
4328c535a9 Improve timeout canceler 2023-03-23 15:39:12 +08:00
世界
f9516709da Update documentation 2023-03-23 07:54:24 +08:00
世界
5dce722879 Update dependencies 2023-03-23 07:49:14 +08:00
世界
9324a39d4e Fix import format 2023-03-20 23:01:54 +08:00
世界
84904c5206 Create working directory if not exists 2023-03-20 19:33:00 +08:00
世界
fe4b429fc2 hysteria: Accept inbound configuration without users 2023-03-20 19:22:46 +08:00
世界
f680d0acaf Add with_reality_server to release build tags 2023-03-20 17:36:59 +08:00
世界
4baff5aeb1 documentation: Update changelog 2023-03-20 17:32:59 +08:00
世界
f25296fb23 Update dependencies 2023-03-20 17:27:48 +08:00
世界
e717852c73 Fix optional listen address 2023-03-19 20:46:22 +08:00
世界
13dc70f649 Fix make build 2023-03-19 16:57:07 +08:00
世界
46040a71c3 Fix vision padding overflow 2023-03-19 10:25:35 +08:00
世界
0558b3fc5c ntp: Add write_to_system service option 2023-03-18 23:11:40 +08:00
世界
99b2ab5526 Add command to fetch a URL 2023-03-18 21:02:29 +08:00
世界
e5f3bb6344 Add command to connect an address 2023-03-18 20:27:38 +08:00
世界
c7f89ad88e Add multiple configuration support 2023-03-18 20:27:38 +08:00
世界
e0d9f79445 Fix test 2023-03-18 17:02:55 +08:00
世界
b6dbb69fc4 Fix write nil in buffered vectorised writer 2023-03-18 16:32:28 +08:00
世界
b76fabee65 documentation: Fix broken link 2023-03-18 16:31:15 +08:00
世界
872bcfd1c0 readme: Add packaging status 2023-03-17 17:58:08 +08:00
世界
b033c13ca2 documentation: Update stable changelog 2023-03-17 17:58:08 +08:00
renovate[bot]
2db188f3a1 dependencies: Update actions/setup-go action to v4
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 17:58:08 +08:00
世界
11de271c8f Add experimental debug options 2023-03-17 17:58:08 +08:00
世界
40c800c57c documentation: Fix typo 2023-03-17 13:34:36 +08:00
世界
91b0540e95 documentation: Update UoT application support status 2023-03-17 13:28:05 +08:00
世界
ce6d186345 Update documentation 2023-03-17 13:07:22 +08:00
世界
32bc4450a7 Update dependencies 2023-03-17 12:59:12 +08:00
世界
43f31b40ba Update UoT protocol 2023-03-17 12:57:48 +08:00
世界
a3a5185b15 platform: Fix bytes format 2023-03-16 11:28:54 +08:00
世界
14a0f180c8 ios: Add with_quic tag in build 2023-03-16 11:28:54 +08:00
世界
cc9cb0b477 platform: Add oom killer 2023-03-16 11:28:54 +08:00
世界
2cb0e37f50 platform: Add low memory interface 2023-03-16 00:36:04 +08:00
世界
dbd5be55b0 tun: Create gVisor stack by default in Apple Network Extension 2023-03-15 21:50:18 +08:00
世界
f674b4fbd5 Fix build embed tor for mobile 2023-03-15 20:59:45 +08:00
世界
5a4e8fea81 Fix lint 2023-03-15 14:56:06 +08:00
世界
78e02b52ca Update UoT protocol 2023-03-15 14:52:32 +08:00
世界
ffdaae90d7 Update dependencies 2023-03-15 11:59:15 +08:00
世界
c77681ea17 Fix close platform tun 2023-03-13 19:47:00 +08:00
世界
d824390167 Fix cross make build 2023-03-13 19:46:20 +08:00
世界
70cf681ff2 Remove length limit on short_id for reality TLS config 2023-03-13 19:46:16 +08:00
wwqgtxx
b004b9ec81 Fix stack wireguard device returning non-nil interface containing nil pointer 2023-03-13 19:46:16 +08:00
世界
657b05fd96 Print command to shell error 2023-03-13 19:46:16 +08:00
世界
caad60da45 Apply --disable-color to global logger 2023-03-13 11:23:00 +08:00
世界
7d22cf9b45 Support $schema in configuration file 2023-03-13 10:58:29 +08:00
世界
5cb178ca93 Update documentation 2023-03-12 23:07:38 +08:00
世界
16788008b6 Update dependencies 2023-03-12 23:07:24 +08:00
世界
6ec7a33046 Fix make install 2023-03-11 19:24:19 +08:00
世界
6af9c2b3ca Add health check support for http-based v2ray transport 2023-03-11 15:49:02 +08:00
世界
bdc620dab1 Fix http server usage 2023-03-11 15:05:07 +08:00
世界
a88820af31 Fix missing default shadowtls version 2023-03-11 10:12:46 +08:00
世界
3688f2e114 Update documentation 2023-03-10 11:15:00 +08:00
世界
a183958d53 Update dependencies 2023-03-09 23:24:05 +08:00
世界
1c8a9e91b7 Generate version during compilation 2023-03-09 23:24:05 +08:00
世界
325f6c71ff Fix windows interface monitor 2023-03-09 16:00:57 +08:00
世界
6c6c0792ad Update reality and uTLS 2023-03-09 10:51:01 +08:00
世界
9264f2307c Fix link broken in installation documentation page 2023-03-08 15:56:41 +08:00
世界
87dd328700 Fix build check 2023-03-08 15:23:46 +08:00
世界
0cec92dd0f Update documentation 2023-03-08 14:59:09 +08:00
世界
e88afa9665 Fix vmess server buffer 2023-03-07 17:07:37 +08:00
世界
3883a81315 Check constant.Version before build release 2023-03-06 16:34:44 +08:00
Dmitry R
c919ad079a systemd: Add reload command 2023-03-06 16:32:54 +08:00
世界
83593aee70 Fix vless read cache 2023-03-06 11:19:38 +08:00
世界
ac7cc09694 Update documentation 2023-03-05 23:37:12 +08:00
世界
d032e3568b Update dependencies 2023-03-05 21:38:02 +08:00
世界
c24df037ac Add documentation for tun platform options 2023-03-05 15:19:13 +08:00
世界
a2d43b3746 Fix open cache file 2023-03-05 14:57:50 +08:00
世界
5b3b74bd0f Fix vision read 2023-03-05 14:57:50 +08:00
世界
d24d3b26dc Fix uTLS randomized fingerprint 2023-03-05 14:57:50 +08:00
seiuneko
5db3cd7781 Fix documentation typo 2023-03-05 14:57:50 +08:00
世界
c88af8b081 Fix documentation 2023-03-05 14:57:50 +08:00
世界
45852ca3e7 Fix check config 2023-03-05 14:57:50 +08:00
Hellojack
03ce555104 Add generate commands 2023-03-05 11:21:32 +08:00
世界
dd0a07624e Add stop platform command 2023-03-04 00:40:47 +08:00
世界
b9b2b77814 Add reload platform command 2023-03-03 21:59:54 +08:00
世界
2366835121 Fix close conn 2023-03-03 19:27:30 +08:00
database64128
42e1dea7d2 Update .gitignore 2023-03-03 18:51:33 +08:00
Ella Hollywood
13d7716b02 Fix documentation typo 2023-03-03 16:35:06 +08:00
世界
7ecb9fc738 Minor fixes 2023-03-03 16:31:07 +08:00
世界
19b15e0d10 Fix UoT UDP address 2023-03-03 11:34:51 +08:00
database64128
0b15de461b Update tfo-go 2023-03-03 10:16:38 +08:00
世界
27aba99e6c Fix command client connect 2023-03-02 16:40:28 +08:00
世界
8151bcfd6b Add ios memory limit 2023-03-02 15:04:59 +08:00
世界
e8802357e1 Fix vless tests 2023-03-02 00:31:56 +08:00
世界
6e22c004f6 Improve server error handling 2023-03-02 00:18:35 +08:00
世界
20e1caa531 Fix custom tls server listener 2023-03-02 00:01:40 +08:00
世界
32ad3c3db3 Remove okhttp form modern fingerprint list 2023-03-01 21:17:30 +08:00
世界
1f5f8a7dde Fixed user flow in vless server 2023-03-01 20:28:40 +08:00
世界
6da1460795 Fix geo resource download path 2023-03-01 19:09:21 +08:00
世界
b14ae51f71 Fix create badhttp2 server 2023-03-01 19:09:21 +08:00
世界
5af8d001ae Refactor platform command api 2023-03-01 19:09:21 +08:00
世界
0ca344df5f Fix uTLS ALPN 2023-02-28 21:16:31 +08:00
世界
49f568abbd Separate uTLS random fingerprint 2023-02-28 21:10:11 +08:00
世界
3b4e811907 Add reality client fallback 2023-02-28 20:55:14 +08:00
世界
d0e9443031 Enable XUDP by default in VLESS 2023-02-28 20:52:26 +08:00
世界
f7e9d9ab1f Fix check early conn 2023-02-28 20:16:15 +08:00
世界
7834d6bca7 Add tun platform options 2023-02-28 19:02:27 +08:00
世界
ed50257735 Add custom TLS server support for http based v2ray transports 2023-02-28 13:03:44 +08:00
世界
f15f525c5c Merge tls interface to library 2023-02-28 11:30:46 +08:00
世界
e4bff0460d Update vision protocol 2023-02-27 15:07:15 +08:00
世界
5ce3ddee9b Add early conn interface 2023-02-26 23:08:20 +08:00
世界
22bf7a9509 Update reality server 2023-02-26 20:55:36 +08:00
世界
842730707c Update TUN creation 2023-02-26 20:55:15 +08:00
世界
a8f13bd956 Fix documentation 2023-02-25 17:25:56 +08:00
世界
cd5c2a7999 Update documentation 2023-02-25 16:28:39 +08:00
世界
fbc94b9e3e Add VLESS server, vision flow and reality TLS 2023-02-25 16:24:08 +08:00
zakuwaki
e766f25d55 Fix private ip will never be matched 2023-02-24 13:31:49 +08:00
世界
140ed9a4cb Fix platform wrapper 2023-02-24 13:00:49 +08:00
世界
60094884cd Update documentation 2023-02-22 11:45:31 +08:00
H3arn
0e8a4d141a Fix incorrect NTP server address 2023-02-21 23:08:05 +08:00
世界
17b78a6339 Fix documentation 2023-02-21 22:06:12 +08:00
世界
e99741159b Update documentation 2023-02-21 20:54:25 +08:00
世界
6b9603227b Add strict mode support for shadowtls v3 2023-02-21 20:51:26 +08:00
世界
23e8d282a3 Add multiple server names and multi-user support for shadowtls 2023-02-21 16:09:06 +08:00
世界
611d6bbfc5 Add NTP service 2023-02-21 16:09:06 +08:00
世界
f26785c0ba Add uTLS support for shadowtls v3 2023-02-20 21:04:07 +08:00
世界
5bcfb71737 Update dependencies 2023-02-20 21:04:07 +08:00
世界
4135c4974f Merge shadowtls to library 2023-02-20 13:53:06 +08:00
世界
222196b182 Add libbox wrapper 2023-02-20 11:07:49 +08:00
世界
86e55c5c1c Fix tproxy inbound 2023-02-19 18:56:38 +08:00
世界
73c068b96f Update documentation 2023-02-19 17:49:05 +08:00
世界
f516026540 Fix shadowtls in go versiojns below 1.20 2023-02-19 12:02:11 +08:00
dyhkwong
3c5bc842ed Update QUIC v2 version number and initial salt 2023-02-18 23:51:55 +08:00
世界
d8270a66f4 Update release script 2023-02-18 21:14:17 +08:00
世界
123c383eae Fix documentation 2023-02-18 19:33:35 +08:00
世界
67814faf92 Remove TLS min version for shadowtls v3 2023-02-18 19:26:05 +08:00
世界
ec4a0c8497 Update documentation 2023-02-18 15:02:27 +08:00
世界
21cb227bc2 Add ShadowTLS protocol v3 2023-02-18 14:55:47 +08:00
世界
1610bdc5dd Update workflow 2023-02-18 14:28:21 +08:00
世界
3296a2f7b2 Update dependencies 2023-02-18 14:28:21 +08:00
世界
2bd91baad0 Add fallback support for v2ray transport 2023-02-18 14:28:21 +08:00
世界
a624cd9b49 Disable vmess header protection if transport enabled 2023-02-13 05:15:26 +08:00
Tim Xylon
02afba132f Replace deprecated 'set-output' 2023-02-09 22:07:00 +08:00
世界
99890a1af0 Fix socks connect response 2023-02-09 21:25:00 +08:00
世界
437f1f819c Fix lint 2023-02-09 21:01:48 +08:00
世界
92a79e6158 Remove cancel-workflow-action 2023-02-09 18:04:49 +08:00
renovate[bot]
c9efd0a74f [dependencies] Update golang Docker tag to v1.20
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 18:04:16 +08:00
renovate[bot]
9da349748a [dependencies] Update github-actions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 17:42:06 +08:00
世界
2423cbbbfe Add renovate config 2023-02-09 17:39:15 +08:00
Gavin Luo
4833f6d5db Fix systemd service caps for process sniffing 2023-02-09 13:32:31 +08:00
世界
9db3cb5cb7 Update scripts 2023-02-09 13:27:05 +08:00
shadow750d6
c14b353a29 Fix parse hysteria UDP message 2023-02-09 13:20:16 +08:00
世界
19d08b55c8 Update documentation 2023-02-08 17:35:50 +08:00
世界
39514b3ca0 Add v2ray user stats api 2023-02-08 16:50:15 +08:00
世界
7ea9d48987 Add DHCP DNS server support 2023-02-08 16:43:29 +08:00
lyc8503
df3a982141 Add SSH outbound host key validation 2023-02-08 16:20:48 +08:00
世界
687b4509df Add query_type DNS rule item 2023-02-08 16:18:40 +08:00
世界
41ec2e7944 Add support for clash DNS query API 2023-02-08 14:13:33 +08:00
世界
1bd3a9144d Upgrade tfo-go 2023-02-08 14:13:33 +08:00
世界
6e852cc99b Update dependencies 2023-02-08 14:13:15 +08:00
世界
8320dd0b51 Improve vmess request 2023-02-07 14:53:08 +08:00
世界
960d04d172 Fix match geoip 2023-02-07 12:21:00 +08:00
世界
86ea035bdd Fix ipv6 redir port 2023-02-05 14:48:02 +08:00
世界
9b6449dcf4 Fix find NDK on macOS 2023-02-02 16:30:50 +08:00
世界
4e22ac1a35 Update documentation 2023-02-02 16:11:29 +08:00
世界
8a779f6e94 Update dependencies 2023-02-02 15:38:48 +08:00
世界
d461768ffb Fix build with go1.20 2023-02-02 15:25:34 +08:00
Dmitry R
5d41e328d4 ignore domain case in route rules 2023-02-02 15:25:34 +08:00
世界
fe492904e9 Fix auth_user route for naive inbound 2023-01-19 10:47:22 +08:00
世界
168253b851 Fix inbound default DF 2023-01-19 10:36:25 +08:00
Hellojack
05620a369e Fix gRPC lite header
Manually set the first byte to 0x00 (No Compression) since we can not ensure that the buffer is not polluted before.
2023-01-16 16:23:06 +08:00
世界
8e0fe55363 Fix wireguard events 2023-01-15 19:48:50 +08:00
世界
59e521c1db Fix convert netaddr 2023-01-14 20:04:15 +08:00
世界
f32c149738 Bump version 2023-01-14 16:01:07 +08:00
世界
23a35b3c06 Fix create UDP DNS transport from plain IPv6 address 2023-01-13 11:51:20 +08:00
世界
044f9c5d4f Fix write to h2 conn after closed 2023-01-08 15:43:12 +08:00
世界
54f9625bdc Fix DNS log 2023-01-03 17:04:10 +08:00
世界
ff0693be32 Bump version 2023-01-03 10:53:38 +08:00
世界
53d9ad93e3 Improve DNS log 2023-01-03 10:36:18 +08:00
世界
f5c5570bec Skip set windows system proxy bypass list 2022-12-26 12:33:08 +08:00
世界
53f19a6ead Fix override packet conn 2022-12-21 21:58:03 +08:00
世界
cfaf15f429 Fix DNS response TTL 2022-12-19 13:10:57 +08:00
世界
9e67f3b4a5 Fix user from stream packet conn 2022-12-18 16:08:49 +08:00
isnowly
4d2185a2d4 Fix http proxy auth 2022-12-18 16:08:49 +08:00
世界
33f22263ca Update dependencies 2022-12-18 15:40:19 +08:00
世界
d09aa07d21 Fix android i686 compiler 2022-12-11 15:01:54 +08:00
世界
8afb8ca7eb Update documentation 2022-12-11 14:40:03 +08:00
世界
80ed5bf8fb Fix android package 2022-12-11 14:38:01 +08:00
世界
81e7b0b320 Fix linux package 2022-12-11 11:51:25 +08:00
世界
a828c3b5da Fix acme config 2022-12-06 13:36:42 +08:00
世界
c95e4a13a1 Fix vmess packet conn 2022-12-06 13:02:28 +08:00
世界
726a7e19eb Suppress quic-go set DF error 2022-12-06 12:51:52 +08:00
世界
8953ddc6e0 Update workflow 2022-12-03 17:03:04 +08:00
世界
7ebbd58b00 Update documentation 2022-12-03 14:38:52 +08:00
世界
d0095fd0f4 Fix close clash cache 2022-12-03 13:29:37 +08:00
世界
66d8d563eb Add WorkingDirectory for systemd service 2022-11-28 18:30:50 +08:00
世界
4bf96c7eb5 Fix quic stub 2022-11-28 16:06:54 +08:00
世界
f687c25fa9 Update documentation 2022-11-28 13:11:07 +08:00
世界
a92412ecac Fix router 2022-11-28 13:11:07 +08:00
世界
8dcafa5b33 Add trojan-go multiplex support for trojan inbound 2022-11-28 12:51:23 +08:00
世界
7a02cb83a7 Revert "Fix listen packet on address"
This reverts commit d1fe17a4db.
2022-11-28 12:51:23 +08:00
世界
51ce672076 Fix crash when input bad method in shadowsocks multi-user inbound 2022-11-28 12:51:23 +08:00
世界
7734afc40c Update dependencies 2022-11-28 12:51:23 +08:00
世界
ee3cd49aa5 Fix tls config for h2 server 2022-11-26 14:55:51 +08:00
世界
bf20ff84b5 Fix lint 2022-11-26 11:21:24 +08:00
世界
c58302554c Fix documentation 2022-11-26 11:06:57 +08:00
418 changed files with 15574 additions and 4114 deletions

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

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

View File

@@ -3,8 +3,7 @@ name: Debug build
on: on:
push: push:
branches: branches:
- main - main-next
- dev
- dev-next - dev-next
paths-ignore: paths-ignore:
- '**.md' - '**.md'
@@ -12,8 +11,7 @@ on:
- '!.github/workflows/debug.yml' - '!.github/workflows/debug.yml'
pull_request: pull_request:
branches: branches:
- main - main-next
- dev
- dev-next - dev-next
jobs: jobs:
@@ -21,28 +19,18 @@ jobs:
name: Debug build name: Debug build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v4
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Add cache to Go proxy - name: Add cache to Go proxy
run: | run: |
version=`git rev-parse HEAD` version=`git rev-parse HEAD`
@@ -60,22 +48,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v4
with: with:
go-version: 1.18.7 go-version: 1.18.10
- name: Cache go module - name: Cache go module
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: | path: |
~/go/pkg/mod ~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }} key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test - name: Run Test
run: | run: make
go test -v ./...
cross: cross:
strategy: strategy:
matrix: matrix:
@@ -192,28 +179,22 @@ jobs:
TAGS: with_clash_api,with_quic TAGS: with_clash_api,with_quic
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get latest go version - name: Get latest go version
id: version id: version
run: | run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v4
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build - name: Build
id: build id: build
run: make run: make
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: sing-box-${{ matrix.name }} name: sing-box-${{ matrix.name }}
path: sing-box* path: sing-box*

View File

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

View File

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

View File

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

View File

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

9
.gitignore vendored
View File

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

View File

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

View File

@@ -1,6 +1,7 @@
project_name: sing-box project_name: sing-box
builds: builds:
- main: ./cmd/sing-box - id: main
main: ./cmd/sing-box
flags: flags:
- -v - -v
- -trimpath - -trimpath
@@ -9,19 +10,17 @@ builds:
gcflags: gcflags:
- all=-trimpath={{.Env.GOPATH}} - all=-trimpath={{.Env.GOPATH}}
ldflags: ldflags:
- -s -w -buildid= - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags: tags:
- with_gvisor - with_gvisor
- with_quic - with_quic
- with_wireguard - with_wireguard
- with_utls - with_utls
- with_reality_server
- with_clash_api - with_clash_api
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
targets: targets:
- android_arm64
- android_amd64
- android_amd64_v3
- linux_amd64_v1 - linux_amd64_v1
- linux_amd64_v3 - linux_amd64_v3
- linux_arm64 - linux_arm64
@@ -35,6 +34,54 @@ builds:
- darwin_amd64_v3 - darwin_amd64_v3
- darwin_arm64 - darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
env:
- CGO_ENABLED=1
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot: snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}" name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives: archives:

View File

@@ -1,4 +1,4 @@
FROM golang:1.19-alpine AS builder FROM golang:1.20-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box
@@ -8,9 +8,10 @@ ENV CGO_ENABLED=0
RUN set -ex \ RUN set -ex \
&& apk add git build-base \ && apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \ && export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \ && export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
-o /go/bin/sing-box \ -o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box ./cmd/sing-box
FROM alpine AS dist FROM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>" LABEL maintainer="nekohasekai <contact-git@sekai.icu>"

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,10 @@ import (
type ClashServer interface { type ClashServer interface {
Service Service
PreStarter
Mode() string Mode() string
StoreSelected() bool StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
@@ -21,6 +23,7 @@ type ClashServer interface {
type ClashCacheFile interface { type ClashCacheFile interface {
LoadSelected(group string) string LoadSelected(group string) string
StoreSelected(group string, selected string) error StoreSelected(group string, selected string) error
FakeIPStorage
} }
type Tracker interface { type Tracker interface {
@@ -32,6 +35,11 @@ type OutboundGroup interface {
All() []string All() []string
} }
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context, url string) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string { func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup { if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now() return group.Now()
@@ -45,6 +53,6 @@ type V2RayServer interface {
} }
type V2RayStatsService interface { type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
} }

23
adapter/fakeip.go Normal file
View File

@@ -0,0 +1,23 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing-dns"
)
type FakeIPStore interface {
Service
Contains(address netip.Addr) bool
Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)
Reset() error
}
type FakeIPStorage interface {
FakeIPMetadata() *FakeIPMetadata
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
FakeIPStore(address netip.Addr, domain string) error
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPReset() error
}

View File

@@ -0,0 +1,50 @@
package adapter
import (
"bytes"
"encoding"
"encoding/binary"
"io"
"net/netip"
"github.com/sagernet/sing/common"
)
type FakeIPMetadata struct {
Inet4Range netip.Prefix
Inet6Range netip.Prefix
Inet4Current netip.Addr
Inet6Current netip.Addr
}
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
var buffer bytes.Buffer
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
data, err = marshaler.MarshalBinary()
if err != nil {
return
}
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
buffer.Write(data)
}
data = buffer.Bytes()
return
}
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
var length uint16
common.Must(binary.Read(reader, binary.BigEndian, &length))
element := make([]byte, length)
_, err := io.ReadFull(reader, element)
if err != nil {
return err
}
err = unmarshaler.UnmarshalBinary(element)
if err != nil {
return err
}
}
return nil
}

View File

@@ -27,7 +27,7 @@ type InjectableInbound interface {
type InboundContext struct { type InboundContext struct {
Inbound string Inbound string
InboundType string InboundType string
IPVersion int IPVersion uint8
Network string Network string
Source M.Socksaddr Source M.Socksaddr
Destination M.Socksaddr Destination M.Socksaddr
@@ -46,6 +46,10 @@ type InboundContext struct {
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string
ProcessInfo *process.Info ProcessInfo *process.Info
// dns cache
QueryType uint16
} }
type inboundContextKey struct{} type inboundContextKey struct{}

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -17,3 +18,8 @@ type Outbound interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }
type IPOutbound interface {
Outbound
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
}

15
adapter/prestart.go Normal file
View File

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

View File

@@ -21,8 +21,13 @@ type Router interface {
Outbound(tag string) (Outbound, bool) Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound DefaultOutbound(network string) Outbound
FakeIPStore() FakeIPStore
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
NatRequired(outbound string) bool
GeoIPReader() *geoip.Reader GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)
@@ -32,13 +37,19 @@ type Router interface {
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
InterfaceFinder() control.InterfaceFinder InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultInterface() string DefaultInterface() string
AutoDetectInterface() bool AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager PackageManager() tun.PackageManager
Rules() []Rule Rules() []Rule
IPRules() []IPRule
TimeService
ClashServer() ClashServer ClashServer() ClashServer
SetClashServer(server ClashServer) SetClashServer(server ClashServer)
@@ -47,6 +58,20 @@ type Router interface {
SetV2RayServer(server V2RayServer) SetV2RayServer(server V2RayServer)
} }
type routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
}
type Rule interface { type Rule interface {
Service Service
Type() string Type() string
@@ -59,6 +84,12 @@ type Rule interface {
type DNSRule interface { type DNSRule interface {
Rule Rule
DisableCache() bool DisableCache() bool
RewriteTTL() *uint32
}
type IPRule interface {
Rule
Action() tun.ActionType
} }
type InterfaceUpdateListener interface { type InterfaceUpdateListener interface {

8
adapter/time.go Normal file
View File

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

View File

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

283
box.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental" "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/inbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@@ -23,85 +24,61 @@ import (
var _ adapter.Service = (*Box)(nil) var _ adapter.Service = (*Box)(nil)
type Box struct { type Box struct {
createdAt time.Time createdAt time.Time
router adapter.Router router adapter.Router
inbounds []adapter.Inbound inbounds []adapter.Inbound
outbounds []adapter.Outbound outbounds []adapter.Outbound
logFactory log.Factory logFactory log.Factory
logger log.ContextLogger logger log.ContextLogger
logFile *os.File preServices map[string]adapter.Service
clashServer adapter.ClashServer postServices map[string]adapter.Service
v2rayServer adapter.V2RayServer done chan struct{}
done chan struct{}
} }
func New(ctx context.Context, options option.Options) (*Box, error) { type Options struct {
createdAt := time.Now() option.Options
logOptions := common.PtrValueOrDefault(options.Log) Context context.Context
PlatformInterface platform.Interface
}
func New(options Options) (*Box, error) {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool var needClashAPI bool
var needV2RayAPI bool var needV2RayAPI bool
if options.Experimental != nil { if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" { needClashAPI = true
needClashAPI = true
}
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
} }
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
var logFactory log.Factory needV2RayAPI = true
var observableLogFactory log.ObservableFactory }
var logFile *os.File var defaultLogWriter io.Writer
if logOptions.Disabled { if options.PlatformInterface != nil {
observableLogFactory = log.NewNOPFactory() defaultLogWriter = io.Discard
logFactory = observableLogFactory }
} else { logFactory, err := log.New(log.Options{
var logWriter io.Writer Options: common.PtrValueOrDefault(options.Log),
switch logOptions.Output { Observable: needClashAPI,
case "", "stderr": DefaultWriter: defaultLogWriter,
logWriter = os.Stderr BaseTime: createdAt,
case "stdout": PlatformWriter: options.PlatformInterface,
logWriter = os.Stdout })
default: if err != nil {
var err error return nil, E.Cause(err, "create log factory")
logFile, err = os.OpenFile(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,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
logFactory.SetLevel(logLevel)
} else {
logFactory.SetLevel(log.LevelTrace)
}
} }
router, err := route.NewRouter( router, err := route.NewRouter(
ctx, ctx,
logFactory.NewLogger("router"), logFactory,
logFactory.NewLogger("dns"),
common.PtrValueOrDefault(options.Route), common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds, options.Inbounds,
options.PlatformInterface,
) )
if err != nil { if err != nil {
return nil, E.Cause(err, "parse route options") return nil, E.Cause(err, "parse route options")
@@ -121,6 +98,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router, router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions, inboundOptions,
options.PlatformInterface,
) )
if err != nil { if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]") return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -139,6 +117,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
ctx, ctx,
router, router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions) outboundOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]") return nil, E.Cause(err, "parse outbound[", i, "]")
@@ -146,7 +125,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds = append(outbounds, out) outbounds = append(outbounds, out)
} }
err = router.Initialize(inbounds, 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"}) out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr) common.Must(oErr)
outbounds = append(outbounds, out) outbounds = append(outbounds, out)
return out return out
@@ -154,37 +133,62 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if options.PlatformInterface != nil {
var clashServer adapter.ClashServer err = options.PlatformInterface.Initialize(ctx, router)
var v2rayServer adapter.V2RayServer if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}
}
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needClashAPI { if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI)) clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil { if err != nil {
return nil, E.Cause(err, "create clash api server") return nil, E.Cause(err, "create clash api server")
} }
router.SetClashServer(clashServer) router.SetClashServer(clashServer)
preServices["clash api"] = clashServer
} }
if needV2RayAPI { if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI)) v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil { if err != nil {
return nil, E.Cause(err, "create v2ray api server") return nil, E.Cause(err, "create v2ray api server")
} }
router.SetV2RayServer(v2rayServer) router.SetV2RayServer(v2rayServer)
preServices["v2ray api"] = v2rayServer
} }
return &Box{ return &Box{
router: router, router: router,
inbounds: inbounds, inbounds: inbounds,
outbounds: outbounds, outbounds: outbounds,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
logFile: logFile, preServices: preServices,
clashServer: clashServer, postServices: postServices,
v2rayServer: v2rayServer, done: make(chan struct{}),
done: make(chan struct{}),
}, nil }, nil
} }
func (s *Box) PreStart() error {
err := s.preStart()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) Start() error { func (s *Box) Start() error {
err := s.start() err := s.start()
if err != nil { if err != nil {
@@ -198,54 +202,70 @@ func (s *Box) Start() error {
} }
}() }()
s.Close() s.Close()
return err
} }
return err s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
} }
func (s *Box) start() error { func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
s.logger.Trace("pre-start ", serviceName)
err := adapter.PreStart(service)
if err != nil {
return E.Cause(err, "pre-starting ", serviceName)
}
}
for i, out := range s.outbounds { for i, out := range s.outbounds {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
if starter, isStarter := out.(common.Starter); isStarter { if starter, isStarter := out.(common.Starter); isStarter {
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
err := starter.Start() err := starter.Start()
if err != nil { 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, "]") return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
} }
} }
} }
err := s.router.Start() return s.router.Start()
}
func (s *Box) start() error {
err := s.preStart()
if err != nil { if err != nil {
return err return err
} }
for serviceName, service := range s.preServices {
s.logger.Trace("starting ", serviceName)
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for i, in := range s.inbounds { for i, in := range s.inbounds {
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
err = in.Start() err = in.Start()
if err != nil { if err != nil {
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
} }
} }
if s.clashServer != nil { for serviceName, service := range s.postServices {
err = s.clashServer.Start() s.logger.Trace("starting ", service)
err = service.Start()
if err != nil { if err != nil {
return E.Cause(err, "start clash api server") return E.Cause(err, "start ", serviceName)
} }
} }
if s.v2rayServer != nil {
err = s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil return nil
} }
@@ -256,19 +276,44 @@ func (s *Box) Close() error {
default: default:
close(s.done) close(s.done)
} }
for _, in := range s.inbounds { var errors error
in.Close() for serviceName, service := range s.postServices {
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
} }
for _, out := range s.outbounds { for i, in := range s.inbounds {
common.Close(out) s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
} }
return common.Close( for i, out := range s.outbounds {
s.router, s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
s.logFactory, errors = E.Append(errors, common.Close(out), func(err error) error {
s.clashServer, return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
s.v2rayServer, })
common.PtrOrNil(s.logFile), }
) s.logger.Trace("closing router")
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
for serviceName, service := range s.preServices {
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
}
s.logger.Trace("closing log factory")
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
return errors
} }
func (s *Box) Router() adapter.Router { func (s *Box) Router() adapter.Router {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,10 +5,15 @@ import (
"io" "io"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
runtimeDebug "runtime/debug" runtimeDebug "runtime/debug"
"sort"
"strings"
"syscall" "syscall"
"time"
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -31,29 +36,88 @@ func init() {
mainCommand.AddCommand(commandRun) mainCommand.AddCommand(commandRun)
} }
func readConfig() (option.Options, error) { type OptionsEntry struct {
content []byte
path string
options option.Options
}
func readConfigAt(path string) (*OptionsEntry, error) {
var ( var (
configContent []byte configContent []byte
err error err error
) )
if configPath == "stdin" { if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin) configContent, err = io.ReadAll(os.Stdin)
} else { } else {
configContent, err = os.ReadFile(configPath) configContent, err = os.ReadFile(path)
} }
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "read config") return nil, E.Cause(err, "read config at ", path)
} }
var options option.Options var options option.Options
err = options.UnmarshalJSON(configContent) err = options.UnmarshalJSON(configContent)
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "decode config") return nil, E.Cause(err, "decode config at ", path)
} }
return options, nil return &OptionsEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readConfig() ([]*OptionsEntry, error) {
var optionsList []*OptionsEntry
for _, path := range configPaths {
optionsEntry, err := readConfigAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range configDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read config directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
}
sort.Slice(optionsList, func(i, j int) bool {
return optionsList[i].path < optionsList[j].path
})
return optionsList, nil
}
func readConfigAndMerge() (option.Options, error) {
optionsList, err := readConfig()
if err != nil {
return option.Options{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var mergedOptions option.Options
for _, options := range optionsList {
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}
}
return mergedOptions, nil
} }
func create() (*box.Box, context.CancelFunc, error) { func create() (*box.Box, context.CancelFunc, error) {
options, err := readConfig() options, err := readConfigAndMerge()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -64,7 +128,10 @@ func create() (*box.Box, context.CancelFunc, error) {
options.Log.DisableColor = true options.Log.DisableColor = true
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options) instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err != nil { if err != nil {
cancel() cancel()
return nil, nil, E.Cause(err, "create service") return nil, nil, E.Cause(err, "create service")
@@ -111,7 +178,10 @@ func run() error {
} }
} }
cancel() cancel()
closeCtx, closed := context.WithCancel(context.Background())
go closeMonitor(closeCtx)
instance.Close() instance.Close()
closed()
if osSignal != syscall.SIGHUP { if osSignal != syscall.SIGHUP {
return nil return nil
} }
@@ -119,3 +189,13 @@ func run() error {
} }
} }
} }
func closeMonitor(ctx context.Context) {
time.Sleep(3 * time.Second)
select {
case <-ctx.Done():
return
default:
}
log.Fatal("sing-box did not close!")
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
//go:build go1.19 && !go1.20 //go:build go1.20 && !go1.21
package badtls package badtls
@@ -14,39 +14,60 @@ import (
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
) )
type Conn struct { type Conn struct {
*tls.Conn *tls.Conn
writer N.ExtendedWriter writer N.ExtendedWriter
activeCall *int32 isHandshakeComplete *atomic.Bool
closeNotifySent *bool activeCall *atomic.Int32
version *uint16 closeNotifySent *bool
rand io.Reader version *uint16
halfAccess *sync.Mutex rand io.Reader
halfError *error halfAccess *sync.Mutex
cipher cipher.AEAD halfError *error
explicitNonceLen int cipher cipher.AEAD
halfPtr uintptr explicitNonceLen int
halfSeq []byte halfPtr uintptr
halfScratchBuf []byte halfSeq []byte
halfScratchBuf []byte
} }
func Create(conn *tls.Conn) (TLSConn, error) { func TryCreate(conn aTLS.Conn) aTLS.Conn {
if !handshakeComplete(conn) { tlsConn, ok := conn.(*tls.Conn)
if !ok {
return conn
}
badConn, err := Create(tlsConn)
if err != nil {
log.Warn("initialize badtls: ", err)
return conn
}
return badConn
}
func Create(conn *tls.Conn) (aTLS.Conn, error) {
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid isHandshakeComplete")
}
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
if !isHandshakeComplete.Load() {
return nil, E.New("handshake not finished") return nil, E.New("handshake not finished")
} }
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawActiveCall := rawConn.FieldByName("activeCall") rawActiveCall := rawConn.FieldByName("activeCall")
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 { if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid active call") return nil, E.New("badtls: invalid active call")
} }
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr())) activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
rawHalfConn := rawConn.FieldByName("out") rawHalfConn := rawConn.FieldByName("out")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct { if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn") return nil, E.New("badtls: invalid half conn")
@@ -108,19 +129,20 @@ func Create(conn *tls.Conn) (TLSConn, error) {
} }
halfScratchBuf := rawHalfScratchBuf.Bytes() halfScratchBuf := rawHalfScratchBuf.Bytes()
return &Conn{ return &Conn{
Conn: conn, Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()), writer: bufio.NewExtendedWriter(conn.NetConn()),
activeCall: activeCall, isHandshakeComplete: isHandshakeComplete,
closeNotifySent: closeNotifySent, activeCall: activeCall,
version: version, closeNotifySent: closeNotifySent,
halfAccess: halfAccess, version: version,
halfError: halfError, halfAccess: halfAccess,
cipher: aeadCipher, halfError: halfError,
explicitNonceLen: explicitNonceLen, cipher: aeadCipher,
rand: randReader, explicitNonceLen: explicitNonceLen,
halfPtr: rawHalfConn.UnsafeAddr(), rand: randReader,
halfSeq: halfSeq, halfPtr: rawHalfConn.UnsafeAddr(),
halfScratchBuf: halfScratchBuf, halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
}, nil }, nil
} }
@@ -130,15 +152,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
return common.Error(c.Write(buffer.Bytes())) return common.Error(c.Write(buffer.Bytes()))
} }
for { for {
x := atomic.LoadInt32(c.activeCall) x := c.activeCall.Load()
if x&1 != 0 { if x&1 != 0 {
return net.ErrClosed return net.ErrClosed
} }
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) { if c.activeCall.CompareAndSwap(x, x+2) {
break break
} }
} }
defer atomic.AddInt32(c.activeCall, -2) defer c.activeCall.Add(-2)
c.halfAccess.Lock() c.halfAccess.Lock()
defer c.halfAccess.Unlock() defer c.halfAccess.Unlock()
if err := *c.halfError; err != nil { if err := *c.halfError; err != nil {
@@ -186,6 +208,7 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead())) binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
} }
incSeq(c.halfPtr) incSeq(c.halfPtr)
log.Trace("badtls write ", buffer.Len())
return c.writer.WriteBuffer(buffer) return c.writer.WriteBuffer(buffer)
} }

View File

@@ -1,4 +1,4 @@
//go:build !go1.19 || go1.20 //go:build !go1.19 || go1.21
package badtls package badtls

View File

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

View File

@@ -1,9 +1,8 @@
//go:build go1.19 && !go.1.20 //go:build go1.20 && !go.1.21
package badtls package badtls
import ( import (
"crypto/tls"
"reflect" "reflect"
_ "unsafe" _ "unsafe"
) )
@@ -16,9 +15,6 @@ const (
//go:linkname errShutdown crypto/tls.errShutdown //go:linkname errShutdown crypto/tls.errShutdown
var errShutdown error var errShutdown error
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
func handshakeComplete(conn *tls.Conn) bool
//go:linkname incSeq crypto/tls.(*halfConn).incSeq //go:linkname incSeq crypto/tls.(*halfConn).incSeq
func incSeq(conn uintptr) func incSeq(conn uintptr)

View File

@@ -0,0 +1,114 @@
package badversion
import (
"strconv"
"strings"
F "github.com/sagernet/sing/common/format"
)
type Version struct {
Major int
Minor int
Patch int
PreReleaseIdentifier string
PreReleaseVersion int
}
func (v Version) After(anotherVersion Version) bool {
if v.Major > anotherVersion.Major {
return true
} else if v.Major < anotherVersion.Major {
return false
}
if v.Minor > anotherVersion.Minor {
return true
} else if v.Minor < anotherVersion.Minor {
return false
}
if v.Patch > anotherVersion.Patch {
return true
} else if v.Patch < anotherVersion.Patch {
return false
}
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
return true
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
return false
}
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
return true
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
return false
}
}
return false
}
func (v Version) String() string {
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
}
return version
}
func (v Version) BadString() string {
version := F.ToString(v.Major, ".", v.Minor)
if v.Patch > 0 {
version = F.ToString(version, ".", v.Patch)
}
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier)
if v.PreReleaseVersion > 0 {
version = F.ToString(version, v.PreReleaseVersion)
}
}
return version
}
func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-")
versionName = parts[0]
identifier := parts[1]
if strings.Contains(identifier, ".") {
identifierParts := strings.Split(identifier, ".")
version.PreReleaseIdentifier = identifierParts[0]
if len(identifierParts) >= 2 {
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
}
} else {
if strings.HasPrefix(identifier, "alpha") {
version.PreReleaseIdentifier = "alpha"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
} else if strings.HasPrefix(identifier, "beta") {
version.PreReleaseIdentifier = "beta"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
} else {
version.PreReleaseIdentifier = identifier
}
}
}
versionElements := strings.Split(versionName, ".")
versionLen := len(versionElements)
if versionLen >= 1 {
version.Major, _ = strconv.Atoi(versionElements[0])
}
if versionLen >= 2 {
version.Minor, _ = strconv.Atoi(versionElements[1])
}
if versionLen >= 3 {
version.Patch, _ = strconv.Atoi(versionElements[2])
}
return
}

View File

@@ -0,0 +1,17 @@
package badversion
import "github.com/sagernet/sing-box/common/json"
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (v *Version) UnmarshalJSON(data []byte) error {
var version string
err := json.Unmarshal(data, &version)
if err != nil {
return err
}
*v = Parse(version)
return nil
}

View File

@@ -0,0 +1,18 @@
package badversion
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCompareVersion(t *testing.T) {
t.Parallel()
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
require.True(t, Parse("1.4").After(Parse("1.3")))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,50 +6,14 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/warning" "github.com/sagernet/sing-box/common/dialer/conntrack"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
"github.com/database64128/tfo-go/v2"
)
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
},
"outbound option `bind_interface` is only supported on Linux and Windows",
)
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
func() bool {
return !C.IsLinux
},
"outbound option `routing_mark` is only supported on Linux",
)
var warnReuseAdderOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
},
"outbound option `reuse_addr` is unsupported on current platform",
)
var warnProtectPathOnNonAndroid = warning.New(
func() bool {
return !C.IsAndroid
},
"outbound option `protect_path` is only supported on Android",
)
var warnTFOOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
},
"outbound option `tcp_fast_open` is unsupported on current platform",
) )
type DefaultDialer struct { type DefaultDialer struct {
@@ -66,20 +30,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
var dialer net.Dialer var dialer net.Dialer
var listener net.ListenConfig var listener net.ListenConfig
if options.BindInterface != "" { if options.BindInterface != "" {
warnBindInterfaceOnUnsupportedPlatform.Check()
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1) bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() { } else if router.AutoDetectInterface() {
const useInterfaceName = C.IsLinux bindFunc := router.AutoDetectInterfaceFunc()
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
remoteAddr := M.ParseSocksaddr(address).Addr
if C.IsLinux {
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
})
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if router.DefaultInterface() != "" { } else if router.DefaultInterface() != "" {
@@ -88,7 +43,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} }
if options.RoutingMark != 0 { if options.RoutingMark != 0 {
warnRoutingMarkOnUnsupportedPlatform.Check()
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
} else if router.DefaultMark() != 0 { } else if router.DefaultMark() != 0 {
@@ -96,11 +50,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark())) listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
} }
if options.ReuseAddr { if options.ReuseAddr {
warnReuseAdderOnUnsupportedPlatform.Check()
listener.Control = control.Append(listener.Control, control.ReuseAddr()) listener.Control = control.Append(listener.Control, control.ReuseAddr())
} }
if options.ProtectPath != "" { if options.ProtectPath != "" {
warnProtectPathOnNonAndroid.Check()
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath)) dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath)) listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
} }
@@ -109,9 +61,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
} else { } else {
dialer.Timeout = C.TCPTimeout dialer.Timeout = C.TCPTimeout
} }
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
}
var udpFragment bool var udpFragment bool
if options.UDPFragment != nil { if options.UDPFragment != nil {
udpFragment = *options.UDPFragment udpFragment = *options.UDPFragment
@@ -162,26 +111,36 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkUDP: case N.NetworkUDP:
if !address.IsIPv6() { if !address.IsIPv6() {
return d.udpDialer4.DialContext(ctx, network, address.String()) return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else { } else {
return d.udpDialer6.DialContext(ctx, network, address.String()) return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
} }
} }
if !address.IsIPv6() { if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address) return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else { } else {
return DialSlowContext(&d.dialer6, ctx, network, address) return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
} }
} }
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var destinationString string if !destination.IsIPv6() {
if destination.IsValid() && !destination.Addr.IsUnspecified() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
destinationString = destination.String()
} else if !destination.IsIPv6() {
destinationString = d.udpAddr4
} else { } else {
destinationString = d.udpAddr6 return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} }
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, destinationString) }
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewConn(conn)
}
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return conntrack.NewPacketConn(conn)
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -68,11 +69,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses) conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
} }
func (d *ResolveDialer) Upstream() any { func (d *ResolveDialer) Upstream() any {

View File

@@ -1,84 +0,0 @@
package dialer
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
if udpConn, ok := conn.(*net.UDPConn); ok {
return &ResolveUDPConn{udpConn, ctx, router, strategy}
} else {
return &ResolvePacketConn{conn, ctx, router, strategy}
}
}
type ResolveUDPConn struct {
*net.UDPConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
return M.Socksaddr{}, err
}
buffer.Truncate(n)
return M.SocksaddrFromNetIP(addr), nil
}
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
}
func (w *ResolveUDPConn) Upstream() any {
return w.UDPConn
}
type ResolvePacketConn struct {
net.PacketConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
_, addr, err := buffer.ReadPacketFrom(w)
if err != nil {
return M.Socksaddr{}, err
}
return M.SocksaddrFromNet(addr), err
}
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
}
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
}
func (w *ResolvePacketConn) Upstream() any {
return w.PacketConn
}

View File

@@ -12,8 +12,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
"github.com/database64128/tfo-go/v2"
) )
type slowOpenConn struct { type slowOpenConn struct {
@@ -28,7 +27,12 @@ type slowOpenConn struct {
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, 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 { if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
return dialer.DialContext(ctx, network, destination.String(), nil) switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.Dialer.DialContext(ctx, network, destination.String())
default:
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
}
} }
return &slowOpenConn{ return &slowOpenConn{
dialer: dialer, dialer: dialer,
@@ -120,6 +124,10 @@ func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil return c.conn == nil
} }
func (c *slowOpenConn) NeedHandshake() bool {
return c.conn == nil
}
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) { func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
if c.conn != nil { if c.conn != nil {
return bufio.Copy(c.conn, r) return bufio.Copy(c.conn, r)

View File

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

View File

@@ -249,6 +249,10 @@ func (c *ClientConn) WriterReplaceable() bool {
return c.requestWrite return c.requestWrite
} }
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientConn) Upstream() any { func (c *ClientConn) Upstream() any {
return c.Conn return c.Conn
} }
@@ -377,6 +381,10 @@ func (c *ClientPacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr() return c.destination.UDPAddr()
} }
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientPacketConn) Upstream() any { func (c *ClientPacketConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }
@@ -413,7 +421,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
if err != nil { if err != nil {
return return
} }
addr = destination.UDPAddr() if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
var length uint16 var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil { if err != nil {
@@ -514,6 +526,10 @@ func (c *ClientPacketAddrConn) FrontHeadroom() int {
return 2 + M.MaxSocksaddrLength return 2 + M.MaxSocksaddrLength
} }
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientPacketAddrConn) Upstream() any { func (c *ClientPacketAddrConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }

View File

@@ -131,6 +131,10 @@ func (c *ServerConn) FrontHeadroom() int {
return 0 return 0
} }
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerConn) Upstream() any { func (c *ServerConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }
@@ -183,6 +187,10 @@ func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksad
return c.ExtendedConn.WriteBuffer(buffer) return c.ExtendedConn.WriteBuffer(buffer)
} }
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerPacketConn) Upstream() any { func (c *ServerPacketConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }
@@ -245,6 +253,10 @@ func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Soc
return c.ExtendedConn.WriteBuffer(buffer) return c.ExtendedConn.WriteBuffer(buffer)
} }
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerPacketAddrConn) Upstream() any { func (c *ServerPacketAddrConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }

View File

@@ -3,10 +3,12 @@ package process
import ( import (
"context" "context"
"net/netip" "net/netip"
"os/user"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
) )
type Searcher interface { type Searcher interface {
@@ -28,5 +30,15 @@ type Info struct {
} }
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return findProcessInfo(searcher, ctx, network, source, destination) info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
} }

View File

@@ -1,25 +0,0 @@
//go:build linux && !android
package process
import (
"context"
"net/netip"
"os/user"
F "github.com/sagernet/sing/common/format"
)
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
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
}

View File

@@ -1,12 +0,0 @@
//go:build !linux || android
package process
import (
"context"
"net/netip"
)
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

@@ -24,13 +24,13 @@ func (l *Listener) Accept() (net.Conn, error) {
bufReader := std_bufio.NewReader(conn) bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader) header, err := proxyproto.Read(bufReader)
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) { if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
return nil, err return nil, &Error{err}
} }
if bufReader.Buffered() > 0 { if bufReader.Buffered() > 0 {
cache := buf.NewSize(bufReader.Buffered()) cache := buf.NewSize(bufReader.Buffered())
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen()) _, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
if err != nil { if err != nil {
return nil, err return nil, &Error{err}
} }
conn = bufio.NewCachedConn(conn, cache) conn = bufio.NewCachedConn(conn, cache)
} }
@@ -42,3 +42,21 @@ func (l *Listener) Accept() (net.Conn, error) {
} }
return conn, 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

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
) )
var ( var (
@@ -27,9 +28,9 @@ func init() {
func runAsUser(name string, args ...string) error { func runAsUser(name string, args ...string) error {
if os.Getuid() != 0 { if os.Getuid() != 0 {
return common.Exec(name, args...).Attach().Run() return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" { } else if sudoUser != "" {
return common.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run() return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else { } else {
return E.New("set system proxy: unable to set as root") 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) { func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "<local>") err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/http"
) )
@@ -15,5 +16,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
} }

27
common/sniff/http_test.go Normal file
View File

@@ -0,0 +1,27 @@
package sniff_test
import (
"context"
"strings"
"testing"
"github.com/sagernet/sing-box/common/sniff"
"github.com/stretchr/testify/require"
)
func TestSniffHTTP1(t *testing.T) {
t.Parallel()
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, metadata.Domain, "www.google.com")
}
func TestSniffHTTP1WithPort(t *testing.T) {
t.Parallel()
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, metadata.Domain, "www.gov.cn")
}

View File

@@ -13,13 +13,13 @@ import (
const ( const (
VersionDraft29 = 0xff00001d VersionDraft29 = 0xff00001d
Version1 = 0x1 Version1 = 0x1
Version2 = 0x709a50c4 Version2 = 0x6b3343cf
) )
var ( var (
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99} 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} 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 ( const (

View File

@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
} }
err := conn.SetReadDeadline(time.Now().Add(timeout)) err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "set read deadline")
} }
_, err = buffer.ReadOnceFrom(conn) _, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{})) err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "read payload")
} }
var metadata *adapter.InboundContext var metadata *adapter.InboundContext
var errors []error var errors []error

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -50,6 +50,9 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
} }
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) { func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
if link == "" {
link = "https://www.gstatic.com/generate_204"
}
linkURL, err := url.Parse(link) linkURL, err := url.Parse(link)
if err != nil { if err != nil {
return return

View File

@@ -1,31 +0,0 @@
package warning
import (
"sync"
"github.com/sagernet/sing-box/log"
)
type Warning struct {
logger log.Logger
check CheckFunc
message string
checkOnce sync.Once
}
type CheckFunc = func() bool
func New(checkFunc CheckFunc, message string) Warning {
return Warning{
check: checkFunc,
message: message,
}
}
func (w *Warning) Check() {
w.checkOnce.Do(func() {
if w.check() {
log.Warn(w.message)
}
})
}

8
constant/dhcp.go Normal file
View File

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

View File

@@ -3,13 +3,40 @@ package constant
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
) )
const dirName = "sing-box" const dirName = "sing-box"
var resourcePaths []string var (
basePath string
tempPath string
resourcePaths []string
)
func BasePath(name string) string {
if basePath == "" || strings.HasPrefix(name, "/") {
return name
}
return filepath.Join(basePath, name)
}
func CreateTemp(pattern string) (*os.File, error) {
if tempPath == "" {
tempPath = os.TempDir()
}
return os.CreateTemp(tempPath, pattern)
}
func SetBasePath(path string) {
basePath = path
}
func SetTempPath(path string) {
tempPath = path
}
func FindPath(name string) (string, bool) { func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name) name = os.ExpandEnv(name)

3
constant/time.go Normal file
View File

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

View File

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

35
debug.go Normal file
View File

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

35
debug_go118.go Normal file
View File

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

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

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

After

Width:  |  Height:  |  Size: 3.7 KiB

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