Compare commits

..

824 Commits
v1.0 ... v1.7.6

Author SHA1 Message Date
世界
aeb7308e81 documentation: Bump version 2023-12-21 15:25:19 +08:00
世界
bb1ebfda83 documentation: Fix link format 2023-12-21 15:24:05 +08:00
世界
c05c798221 Fix missing UDP timeout for QUIC protocols 2023-12-21 15:16:36 +08:00
世界
55b1bcc6a5 Migrate udp_timeout from seconds to duration format 2023-12-21 14:50:33 +08:00
世界
d6eddce420 Fix missing handshake timeout for multiplex 2023-12-21 14:21:59 +08:00
世界
4bf057139b Fix DNS dial context 2023-12-21 14:19:27 +08:00
世界
a1b28b8282 Try to fix HTTP server leak again 2023-12-21 14:16:16 +08:00
世界
d0aaf71770 Fix direct UDP override 2023-12-18 21:48:59 +08:00
世界
2f31202c6b documentation: Update shadowsocks example
Remove the information on password generation for `2022-blake3-aes-128-gcm` cipher from the Server Example section in the shadowsocks.md file as it is no longer needed.
2023-12-14 21:05:41 +08:00
renovate[bot]
e4cc510712 [dependencies] Update github-actions 2023-12-14 15:14:22 +08:00
世界
e329bf6865 Update dependencies 2023-12-14 15:09:03 +08:00
世界
2badcec765 platform: Fix check config 2023-12-12 20:26:41 +08:00
世界
e71c13b1a2 documentation: Bump version 2023-12-12 13:54:23 +08:00
世界
a959a67ed3 FIx error handling for netlink banned in Android 2023-12-12 13:54:23 +08:00
世界
a1044af579 platform: Fix VPN route 2023-12-11 21:59:59 +08:00
世界
a64b57451a Update dependencies 2023-12-11 21:40:11 +08:00
世界
f0e2318cbd Fix auto-route IPv6 on darwin 2023-12-11 21:39:29 +08:00
世界
ebec308fd8 documentation: Bump version 2023-12-09 00:22:27 +08:00
世界
ca094587be Fix incorrect dependency 2023-12-09 00:22:27 +08:00
世界
ca3b86c781 Update bug report template 2023-12-08 21:56:30 +08:00
世界
5a1d0047b9 documentation: Bump version 2023-12-08 21:38:34 +08:00
世界
4669854039 Fix method check for v2ray HTTP transport 2023-12-08 21:12:52 +08:00
世界
2eecdc38a4 Fix gun conn setup 2023-12-08 21:10:36 +08:00
世界
83581b7c1a Update dependencies 2023-12-08 11:18:52 +08:00
世界
d346f0023d Fix HTTP connect client again 2023-12-08 11:18:52 +08:00
世界
47b7a29cbd Fix fallback packet conn 2023-12-06 18:47:50 +08:00
世界
cffc07579d Fix missing omitempty for NTP server fields 2023-12-05 18:32:21 +08:00
世界
0ef268637e Prevent nil LocalAddr or RemoteAddr 2023-12-05 15:11:09 +08:00
世界
50f5a76380 Update dependencies 2023-12-04 21:06:15 +08:00
世界
20ca05dd36 Fix crash on websocket concurrent request 2023-12-04 20:42:01 +08:00
世界
5a792b186a documentation: Bump version 2023-12-03 16:54:49 +08:00
世界
3f458064a3 Fix not set Host header for HTTP outbound 2023-12-03 16:54:49 +08:00
世界
5269231df0 Fix URLTest outbound 2023-12-02 17:55:58 +08:00
世界
fc8e49994c documentation: Bump version 2023-12-01 20:39:01 +08:00
世界
e911d4aa4b Fix URLTest group early start 2023-12-01 20:39:01 +08:00
世界
01f6e70bc5 Fix deadline usage 2023-12-01 20:39:01 +08:00
世界
5f1e39a42c documentation: Bump version 2023-11-29 21:01:28 +08:00
世界
4f7770e254 Update dependencies 2023-11-29 21:01:19 +08:00
世界
e8c4c942c0 documentation: Bump version & Refactor docs 2023-11-28 11:21:57 +08:00
世界
253976d6c0 Add wifi_ssid and wifi_bssid route and DNS rules 2023-11-28 11:21:44 +08:00
世界
f0571b4122 Update quic-go to v0.40.0 2023-11-28 11:21:44 +08:00
世界
1b71e52e90 Migrate multiplex and UoT server to inbound & Add tcp-brutal support for multiplex 2023-11-28 11:21:44 +08:00
世界
6d24be23da Add support for v2ray http upgrade transport 2023-11-28 11:21:44 +08:00
世界
2a45c178fa Add exclude route support for tun &
Update gVisor to 20231113.0
2023-11-28 11:21:43 +08:00
世界
81e214812f Add udp_disable_domain_unmapping inbound listen option 2023-11-28 11:21:43 +08:00
世界
4d23773a25 Migrate to gobwas/ws 2023-11-28 11:21:43 +08:00
世界
40a0b69918 Fix dhcp reset 2023-11-28 11:20:48 +08:00
世界
a7b37c5953 documentation: Bump version 2023-11-24 20:58:48 +08:00
世界
03663a5093 Fix cachefile permission 2023-11-24 20:58:48 +08:00
世界
b08226a850 Fix "Fix HTTP server leak" 2023-11-24 19:58:31 +08:00
世界
edbae5dc4d Fix missing UDP user context on TUIC/Hysteria2 inbounds 2023-11-24 19:56:43 +08:00
世界
0f8ad0234b Remove unused code 2023-11-24 19:55:53 +08:00
世界
661eadc3bd documentation: Bump version 2023-11-21 10:22:44 +08:00
世界
50c1290567 Update dependencies 2023-11-21 10:22:44 +08:00
世界
eaccc9759a Fix platform API check 2023-11-21 10:22:44 +08:00
世界
925214869b Add test for ss2022 EIH 2023-11-20 18:36:44 +08:00
世界
6a2bfd26d0 Fix QUIC sniffer 2023-11-16 22:48:16 +08:00
世界
72a81afb76 Fix "Fix Linux IPv6 auto route rules" 2023-11-16 18:25:07 +08:00
世界
240abe204c Fix zero TTL was incorrectly reset 2023-11-16 18:25:07 +08:00
世界
7c49196792 build: Fix bad environment key 2023-11-16 01:42:24 +08:00
嫦悅
3a2808cff6 documentation: Fix typo
The old meaning is wrong. Correct the meaning according to the English documentation and the actual effect of the option.

Signed-off-by: 嫦悅 <lomombwlo@gmail.com>
2023-11-16 01:12:26 +08:00
guangwu
005d6cf4cf chore: unnecessary use of fmt.Sprintf 2023-11-16 01:10:52 +08:00
世界
36dff630d6 documentation: Bump version 2023-11-15 14:10:37 +08:00
世界
1825869124 platform: Refactor log interface 2023-11-15 14:10:37 +08:00
世界
3cadc90375 Fix TUIC authentication failed error message 2023-11-14 20:15:41 +08:00
renovate[bot]
2c6967d7f9 dependencies: Update actions/checkout digest to b4ffde6
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 20:15:41 +08:00
世界
fe866b123a Fix sing-tun version 2023-11-14 20:15:41 +08:00
世界
cbef1b1e59 Remove apk build due to missing openrc configuration 2023-11-14 20:15:41 +08:00
世界
e21f84932c build: Fix missing linux/386 2023-11-14 10:35:27 +08:00
0x7d274284
7a679bc328 Fix Dockerfile
Keep the .git folder at compile time to get the correct version

Signed-off-by: 0x7d274284 <112329548+0x7d274284@users.noreply.github.com>
2023-11-14 10:34:38 +08:00
世界
6635dd9abc documentation: Bump version 2023-11-13 21:50:36 +08:00
世界
ce164724ea build: Add apk and archlinux package builds 2023-11-13 15:28:28 +08:00
世界
a3ef7a7d88 Fix trojan-go mux context
Co-authored-by: maskedeken <maskedeken@yahoo.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 14:12:35 +08:00
世界
71218ef0d3 build: Unify build tags 2023-11-13 14:12:35 +08:00
世界
e777b4c6dc Fix not closing outConn
Co-authored-by: Mahdi-zarei  <mahdi.zrei@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 13:54:03 +08:00
世界
6815f94180 build: Update Go to 1.20.11 for legacy builds 2023-11-13 13:47:15 +08:00
世界
b013acd89d tun: Fix broadcast filter not applied to mixed stack 2023-11-13 13:35:10 +08:00
世界
f7c2eb6e76 Fix v2ray ws crash 2023-11-13 13:34:31 +08:00
世界
3ef9b1b343 Update protobuf generated binary 2023-11-10 10:56:25 +08:00
SakuraWald
2224c68959 Fix issue template 2023-11-10 10:36:46 +08:00
Kumiko as a Service
bb7d03d1db Use golang's cross-compilation capabilities 2023-11-10 10:36:32 +08:00
世界
50036924e8 documentation: Bump version 2023-11-10 10:24:18 +08:00
世界
c2c3f7284f Revert "Fix Host ignored in v2ray websocket transport"
This reverts commit aaa6702863.
2023-11-09 16:55:47 +08:00
世界
f6fee53676 Fix mux client close 2023-11-09 16:55:47 +08:00
世界
63b8e8ed23 platform: Increase HTTP timeout to 15s 2023-11-09 16:55:47 +08:00
世界
6ae86eda98 build: Update gradle command 2023-11-09 16:55:47 +08:00
世界
267d9617b7 build: Fix tag calculate 2023-11-07 22:27:37 +08:00
世界
0a06ccae50 platform: Fix legacy code 2023-11-07 22:14:23 +08:00
世界
8de0fad9f5 documentation: Bump version 2023-11-07 10:20:05 +08:00
世界
e05bf6308e Fix build script 2023-11-07 10:20:05 +08:00
世界
a20a0cb455 Add broadcast filter 2023-11-07 10:20:05 +08:00
世界
d29f7475d2 documentation: Bump version 2023-11-06 19:37:45 +08:00
世界
aaa6702863 Fix Host ignored in v2ray websocket transport 2023-11-05 23:27:11 +08:00
世界
bb928f096a Fix missing default next proto in hysteria2 2023-11-05 23:11:40 +08:00
johnthecoderpro
9f01d5c5b4 Fix download geo resources 2023-11-05 16:03:15 +08:00
世界
11629a931b Update release script 2023-11-05 16:02:18 +08:00
世界
126f825241 Update dependencies 2023-11-05 16:02:10 +08:00
世界
998cc7bd22 Add multicast filter for tun 2023-11-04 08:04:17 +08:00
世界
3efccaa8f5 Update dependencies 2023-10-31 18:24:58 +08:00
世界
d57b35ec30 documentation: Add privacy policy for android 2023-10-31 17:32:18 +08:00
世界
e82dab027d documentation: Bump version 2023-10-30 13:59:49 +08:00
世界
9350f3983b docs: Remove obsolete fields 2023-10-30 13:59:49 +08:00
世界
53b123241f android: Add build info tools for debug 2023-10-30 12:41:24 +08:00
世界
97286eea1e Add TLS self sign generate command 2023-10-30 12:41:23 +08:00
世界
343e24969d Add brutal debug option for Hysteria2 2023-10-30 12:41:23 +08:00
世界
31c294d998 Update BBR and Hysteria congestion control & Migrate legacy Hysteria protocol to library 2023-10-30 12:41:22 +08:00
世界
3b161ab30c Fix netip.Prefix usage 2023-10-30 12:41:22 +08:00
septs
41fd1778a7 Improve HTTP headers option 2023-10-30 12:41:22 +08:00
septs
ac930cf1aa Improve naive auth logical 2023-10-30 12:41:22 +08:00
世界
e143fc510d Update gVisor to 20230814.0 2023-10-30 12:41:21 +08:00
世界
bea177a4cd Improve linux bind interface 2023-10-30 12:41:21 +08:00
世界
aa05a4d050 Remove deprecated features 2023-10-30 12:41:21 +08:00
世界
a8112ff824 Update workflows 2023-10-30 12:40:52 +08:00
世界
a7710c3845 documentation: Bump version 2023-10-30 10:42:42 +08:00
世界
cb2e15f8a7 Fix UDP domain NAT 2023-10-28 21:41:39 +08:00
世界
23aa8a0543 Add legacy builds for old Windows and macOS versions 2023-10-26 14:02:24 +08:00
世界
edf7d046eb Fix outbound not found message 2023-10-26 14:02:19 +08:00
世界
de0b5cc1c2 Fix Linux IPv6 auto route rules 2023-10-26 12:02:00 +08:00
世界
2686e8afea Fix TUIC server TLS config not started 2023-10-26 12:01:28 +08:00
世界
d9853ca2be Update dependencies 2023-10-26 11:57:18 +08:00
世界
b617eb5adf documentation: Bump version 2023-10-23 14:09:09 +08:00
世界
ddf38799e2 makefile: Fix release command 2023-10-23 14:09:06 +08:00
世界
5291d43dc8 makefile: Add -allowProvisioningUpdates to Apple build commands 2023-10-21 17:51:00 +08:00
世界
a634830d85 Fix invalid address check in UoT conn 2023-10-21 17:23:30 +08:00
世界
e5d191ca73 Add retry for bbolt open 2023-10-14 19:24:05 +08:00
世界
2371f0fd51 Update dependencies 2023-10-14 17:36:35 +08:00
世界
cfdce7a96f Fix bbolt panic on arm32 2023-10-14 17:36:13 +08:00
世界
dc8ac01dec documentation: Bump version 2023-10-11 12:05:31 +08:00
世界
5f18738b2b Fix task cancel context 2023-10-11 12:05:27 +08:00
世界
7b4e4ca2d0 Fix compatibility with Android 14 2023-10-10 15:09:49 +08:00
世界
01ba4668b6 platform: Also reset connections on wake 2023-10-10 15:08:18 +08:00
dyhkwong
e782d21806 Fix connect domain for IP outbound 2023-10-10 15:08:16 +08:00
世界
00155d61fc documentation: Bump version 2023-10-07 10:04:10 +08:00
世界
8f2273a2b4 documentation: Bump version 2023-10-06 17:10:53 +08:00
世界
0d0526afa2 Update dependencies 2023-10-06 17:10:31 +08:00
世界
ac2d07b61a Fix UDP dialer network 2023-10-03 11:08:31 +08:00
世界
d35487f422 Fix ip_version does not take effect 2023-10-03 11:01:25 +08:00
世界
2749f4a013 documentation: Bump version 2023-10-03 09:22:29 +08:00
世界
45c679648e platform: Fix log server 2023-10-03 09:22:29 +08:00
世界
5f2f7fc8b9 Fix HTTP inbound leak 2023-10-01 14:39:58 +08:00
世界
83c79102cf Update xcode version script 2023-10-01 14:05:51 +08:00
世界
8b95292e53 Update dependencies 2023-09-30 22:34:54 +08:00
世界
3de7a2ddd3 Fix concurrent access on task returnError 2023-09-30 21:52:11 +08:00
Shanoa Ice
8437a6cb4e Fix set KDE system proxy 2023-09-30 16:44:58 +08:00
世界
9c4d08c6e1 documentation: Bump version 2023-09-28 16:02:54 +08:00
世界
e26096085e Remove invalid code 2023-09-28 15:52:05 +08:00
世界
2f1b2199c5 Fix DHCPv4 listen address 2023-09-27 18:25:23 +08:00
世界
af791db01f documentation: Bump version 2023-09-27 14:16:04 +08:00
世界
abcf030d89 Update dependencies 2023-09-27 14:16:04 +08:00
世界
7840dc73e3 Fix resolve dialer 2023-09-27 13:17:16 +08:00
世界
df9050400e android: Fix netlink check 2023-09-26 17:41:07 +08:00
世界
fdd38d6cf8 documentation: Bump version 2023-09-25 21:56:32 +08:00
世界
9891fd672f Reject SOCKS4 unauthenticated request 2023-09-25 21:16:14 +08:00
世界
92a84ee112 Update dependencies 2023-09-25 17:56:21 +08:00
世界
992331f17e documentation: Bump version 2023-09-24 14:40:03 +08:00
世界
4fb227ed86 Fix payload not return to pool 2023-09-24 14:40:03 +08:00
世界
5a1ddea100 Fix dns log 2023-09-24 12:38:33 +08:00
世界
fbaa2f9de9 Fix merge command 2023-09-24 12:12:35 +08:00
世界
97ab9bb194 Fix shadow-tls user context 2023-09-24 12:12:35 +08:00
世界
61ac141124 Improve URLTest delay calculate 2023-09-23 20:27:43 +08:00
世界
d4d49d9df5 Fix ACME DNS01 DNS challenge 2023-09-23 20:26:46 +08:00
世界
c60a944aac Fix missing context in geo resources download 2023-09-23 20:26:46 +08:00
世界
17584c245f documentation: Bump version 2023-09-22 12:08:09 +08:00
世界
6e84b694a4 Minor fixes 2023-09-22 12:07:39 +08:00
世界
34a93171f0 Update dependencies 2023-09-20 22:20:40 +08:00
世界
678f6ef72f Fix debug.SetMemoryLimit usage 2023-09-20 22:01:58 +08:00
世界
ae8187ed15 documentation: Bump version 2023-09-20 14:15:54 +08:00
世界
12dd1ac87f Improve conntrack 2023-09-20 14:15:00 +08:00
世界
85c8f00885 documentation: Bump version 2023-09-19 19:59:29 +08:00
世界
e7b7ae811f Add merge command 2023-09-19 19:59:07 +08:00
世界
a9743b77f6 Don't return success as an error when the lookup result is empty 2023-09-19 19:05:23 +08:00
世界
4068871d97 Update quic-go 2023-09-19 18:33:44 +08:00
世界
f05afcea39 Update dependencies 2023-09-19 18:33:06 +08:00
renovate[bot]
688e9daef4 [dependencies] Update github-actions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-17 13:12:03 +08:00
世界
64edacffb7 Bump version 2023-09-17 01:09:48 +08:00
世界
743df5373b Fix hysteria2 mbps calculation 2023-09-17 01:09:48 +08:00
世界
e80084316d Fix panic on create system proxy failed 2023-09-17 01:09:41 +08:00
世界
9dcd427743 Fix v2ray websocket transport 2023-09-17 01:09:41 +08:00
世界
d17e93384b Add ACME DNS01 challenge support via libdns 2023-09-17 01:09:41 +08:00
世界
c1ffcf365e Fix test 2023-09-16 23:45:47 +08:00
世界
3040e97222 Fix gomobile build on macOS 2023-09-16 00:09:45 +08:00
世界
5f063fb0b5 Update Makefile 2023-09-16 00:09:41 +08:00
世界
a7dadd8671 documentation: Update Clash default_mode description 2023-09-16 00:09:41 +08:00
世界
c320be75a7 Add interrupt support for outbound groups 2023-09-16 00:09:41 +08:00
世界
bd7adcbb7e Migrate QUIC wrapper and protocol implementations to library 2023-09-16 00:09:41 +08:00
世界
1d6d3edec5 documentation: Update changelog for stable versions 2023-09-15 17:34:39 +08:00
世界
46bfeb574c documentation: Bump version 2023-09-12 15:57:33 +08:00
世界
a1449ee40e Mark deprecated features 2023-09-12 13:26:23 +08:00
世界
8cb41b5fa6 documentation: Add hysteria2 2023-09-12 13:26:22 +08:00
世界
53475c7390 Add hysteria2 protocol 2023-09-12 13:26:21 +08:00
世界
5d8af150a7 Improve system proxy API 2023-09-12 13:26:21 +08:00
Hiddify
69499a51a5 Add KDE set system proxy support
Co-authored-by: Hiddify <114227601+hiddify1@users.noreply.github.com>
2023-09-12 13:26:20 +08:00
世界
4c050d7f4b Add ECH support for QUIC based protocols 2023-09-12 13:26:20 +08:00
世界
533fca9fa3 documentation: Update TLS ECH struct 2023-09-12 13:26:20 +08:00
世界
187bf2f7bc Enable with_ech by default 2023-09-12 13:26:19 +08:00
世界
983a4222ad Add ECH keypair generator 2023-09-12 13:26:19 +08:00
世界
2ea506aeb8 Remove legacy NTP usages 2023-09-12 13:26:19 +08:00
世界
5b343d4c72 Improve ECH support 2023-09-12 13:26:19 +08:00
世界
be61ca64d4 Fix SOCKS outbound 2023-09-12 13:26:03 +08:00
世界
efe33cf48d Fix QUIC DNS 2023-09-12 13:14:21 +08:00
世界
fe8d46cce5 Fix TFO async write 2023-09-09 19:52:13 +08:00
世界
b1f289bce5 Fix TUIC context 2023-09-09 11:41:04 +08:00
世界
a8beb80876 Update Makefile 2023-09-07 22:37:55 +08:00
世界
ff209471d8 Fix QUIC defragger 2023-09-07 21:35:25 +08:00
unknown
806f7d0a2b Fix QUIC stream usage 2023-09-07 21:34:36 +08:00
世界
6b943caf37 Reject invalid connection 2023-09-07 21:34:36 +08:00
世界
4ea2d460f4 Fix router close 2023-09-07 21:34:35 +08:00
世界
c84c18f960 platform: Fix crash on android 2023-09-07 21:34:35 +08:00
世界
1402bdab41 Fix connect domain for IP outbounds 2023-09-07 21:34:35 +08:00
renovate[bot]
7082cf277e [dependencies] Update actions/checkout action to v4 2023-09-07 21:34:35 +08:00
世界
b9310154a7 clash-api: Move default mode to first 2023-09-07 21:34:35 +08:00
世界
55c34e3fb0 platform: Improve client 2023-09-07 21:34:35 +08:00
世界
68f2202eec documentation: Bump version 2023-08-31 23:32:44 +08:00
renovate[bot]
5057e50bb8 [dependencies] Update actions/stale action to v8
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-31 23:32:44 +08:00
世界
23e1a69955 Update Makefile 2023-08-31 23:32:44 +08:00
世界
b83c6c9d20 Fix return nil addr in conn 2023-08-30 21:28:03 +08:00
世界
67deac6d44 Fix hysteria packet write 2023-08-30 18:23:17 +08:00
世界
ea3731162b Update Makefile 2023-08-30 18:23:17 +08:00
世界
c75e32e722 documentation: Update changelog 2023-08-29 10:26:11 +08:00
世界
e7b35be5f6 Update Makefile 2023-08-29 10:26:11 +08:00
世界
5a309266f0 Fix TUIC client 2023-08-28 19:58:02 +08:00
世界
05669eaaad documentation: Update changelog 2023-08-25 20:33:16 +08:00
世界
e91a6e5439 Update dependencies 2023-08-25 20:21:14 +08:00
世界
43f72a6419 Add store_mode and platform Clash mode selector 2023-08-25 20:21:14 +08:00
世界
6dcacf3b5e Fix sniffer 2023-08-24 21:53:06 +08:00
世界
edad4d1ce7 Update dependencies 2023-08-24 11:31:29 +08:00
世界
262842c87d Fix test 2023-08-22 11:22:13 +08:00
世界
376f527742 documentation: Bump version 2023-08-21 18:24:31 +08:00
世界
c0bbb3849d Fix TUIC UDP 2023-08-21 18:16:38 +08:00
世界
738c25d818 Fix Makefile 2023-08-21 18:14:27 +08:00
dyhkwong
027af4d4ee Fix SOCKS5 UDP sniffing accidentially enabled 2023-08-20 19:06:37 +08:00
世界
6011f4483a Remove bad scripts 2023-08-20 17:57:39 +08:00
世界
fc22466e3b documentation: Bump version 2023-08-20 17:29:22 +08:00
世界
975e13a313 Add [include/exclude]_interface iproute2 options 2023-08-20 17:29:22 +08:00
世界
f46732bc0e Add udp over stream support for TUIC 2023-08-20 14:15:22 +08:00
世界
5c5c25e3ad Update tfo-go 2023-08-20 13:32:07 +08:00
世界
53a0bf2d11 minor: Remove unused parameter 2023-08-20 13:32:07 +08:00
renovate[bot]
7b79d98f59 [dependencies] Update golang Docker tag to v1.21 2023-08-20 13:31:56 +08:00
世界
1dd2c26f31 Fix UDP route 2023-08-20 13:31:55 +08:00
armv9
d14170348d Fix process search with fakeip 2023-08-20 13:31:55 +08:00
世界
9f94b21687 platform: Add group expand status 2023-08-20 13:31:55 +08:00
世界
cf57e46d69 Add new issue template 2023-08-20 13:31:49 +08:00
世界
b459001600 Update documentation 2023-08-20 13:31:48 +08:00
世界
73267fd6ad Fix ci build 2023-08-20 13:31:48 +08:00
世界
1019ecfdcf Add TCP MultiPath support 2023-08-20 13:31:48 +08:00
世界
81b847faca Pause recurring tasks when no network 2023-08-20 13:31:48 +08:00
世界
ce4c76cdd2 documentation: Add TUIC 2023-08-20 13:31:47 +08:00
世界
917420e79a Add TUIC protocol 2023-08-20 13:31:40 +08:00
世界
0b14dc3228 Update quic-go 2023-08-20 13:31:40 +08:00
世界
cbdaf3272b Update dependencies 2023-08-20 13:31:36 +08:00
世界
d51ab2b0a7 Fix missing HandshakeConn interface 2023-08-20 13:31:31 +08:00
世界
1363e16312 platform: Enable Clash API support by default 2023-08-20 13:31:29 +08:00
世界
f43d0141f3 Save fakeip metadata immediately 2023-08-20 13:31:27 +08:00
世界
90b3aad83a Fix network monitor 2023-08-20 13:30:50 +08:00
世界
2675aff98a Fix tag detect 2023-08-07 22:07:24 +08:00
世界
09ffa2c66e documentation: Update changelog 2023-08-05 21:37:08 +08:00
世界
9fba4f02b6 Update dependencies 2023-08-05 21:37:08 +08:00
世界
59987747e5 platform: Improve status 2023-08-04 21:10:32 +08:00
世界
c40140bbae Fix UDP async write 2023-08-04 12:38:51 +08:00
世界
2123b216c0 Fix http proxy server again 2023-08-04 12:38:51 +08:00
世界
1983f54907 platform: Add sleep support for NetworkExtension 2023-08-04 12:38:51 +08:00
世界
8d629ef323 documentation: Update changelog 2023-07-31 09:49:07 +08:00
世界
f57bee2f4b Update quic-go 2023-07-31 09:49:07 +08:00
世界
679739683e Update dependencies 2023-07-31 09:07:32 +08:00
世界
4fcce1f073 Fix http proxy server 2023-07-31 09:04:55 +08:00
世界
ff14220e08 platform: Update profile binary format 2023-07-30 20:55:27 +08:00
世界
a7b7a5c3c5 documentation:Add tvOS page 2023-07-29 21:09:15 +08:00
世界
b054441f34 platform: Add support for tvOS 2023-07-29 21:01:43 +08:00
世界
1e31d26e03 documentation: Update installation 2023-07-29 21:01:43 +08:00
世界
ffe515d0e0 documentation: Update changelog 2023-07-25 13:21:15 +08:00
世界
aad021f521 Fix gVisor UDP 2023-07-25 08:28:24 +08:00
世界
4a986459ee documentation: Update changelog 2023-07-24 17:56:39 +08:00
世界
9532d0cba4 Fix cache file 2023-07-24 16:51:03 +08:00
世界
cadc34f3ad Add support for macOS system extension 2023-07-24 16:51:03 +08:00
世界
db23a48b36 Update dependencies 2023-07-23 14:23:51 +08:00
世界
407cf68e59 Fix certmagic usage 2023-07-20 20:03:20 +08:00
世界
e0058ca9c5 Fix fakeip unsaved state 2023-07-20 19:59:04 +08:00
世界
8140af01aa Fix download geo resources 2023-07-20 19:41:22 +08:00
世界
98bf696d01 Fix cache file 2023-07-20 19:41:22 +08:00
世界
e075bb5c8d Update changelog 2023-07-19 14:59:30 +08:00
世界
c6baabedef Update dependencies 2023-07-19 14:45:30 +08:00
世界
6e6998dab7 Improve platform status 2023-07-16 14:08:45 +08:00
世界
1a29c23263 documentation: Update changelog 2023-07-15 14:53:48 +08:00
世界
0f87396ab6 Fix abx reader for some malformed formats 2023-07-15 14:46:15 +08:00
世界
ffde948860 Update support page 2023-07-15 14:46:09 +08:00
世界
69b5dbdcc3 Build memory limiter for android 2023-07-15 14:46:06 +08:00
世界
1121517755 Fix hysteria inbound context 2023-07-11 20:58:20 +08:00
世界
6879def619 Fix test 2023-07-11 15:44:03 +08:00
世界
5c0f6d0a6f Fix vmess legacy server 2023-07-11 15:44:03 +08:00
世界
d74abbd20e Fix v2ray websocket transport 2023-07-11 15:44:03 +08:00
世界
120dae4eed Fix fakeip lookup 2023-07-11 15:44:02 +08:00
世界
bb651db2d2 platform: Fix output format 2023-07-09 12:24:43 +08:00
世界
e929dde13e documentation: Update changelog 2023-07-09 07:54:08 +08:00
世界
9d75385bbb Fix async FakeIP save 2023-07-08 16:21:11 +08:00
世界
1c526feec1 Update dependencies 2023-07-08 16:19:28 +08:00
世界
7df26986de documentation: Update changelog 2023-07-07 14:23:45 +08:00
世界
5f2d23a12d Fix multi error 2023-07-07 13:57:28 +08:00
世界
d9e65c0969 Fix syscall copy packet 2023-07-07 13:42:42 +08:00
世界
ec1160924f Fix save FakeIP cache 2023-07-07 12:51:38 +08:00
世界
230e8f895d Fix close quic.Listener 2023-07-07 12:43:22 +08:00
世界
af79378734 Update dependencies 2023-07-03 21:47:32 +08:00
世界
07ce5e0d22 Remove stack buffer usage 2023-07-03 21:45:32 +08:00
世界
9c8565cf21 platform: Add group interface 2023-07-02 18:46:41 +08:00
世界
5ad0ea2b5a Try fix StreamDomainNameQuery 2023-07-02 16:38:38 +08:00
世界
e482053c8a documentation: Update changelog 2023-06-27 11:31:24 +08:00
世界
945713d886 Update dependencies 2023-06-27 11:13:30 +08:00
世界
9bb62ad6b5 Check duplicated outbound tag 2023-06-26 18:47:52 +08:00
世界
c2bda9fbde Fix DNS rewrite_ttl logic 2023-06-23 16:12:25 +08:00
世界
1d1db62a44 Prevent write packet IPv6 packet to tun IPv4 connection 2023-06-21 13:29:24 +08:00
世界
39405373f8 documentation: Update changelog 2023-06-19 14:11:41 +08:00
世界
22a7988d3f Update dependencies 2023-06-19 14:11:41 +08:00
世界
b2092fafb7 Fix ios build
Cannot use errno as method and variable due to conflict with objc
2023-06-19 14:11:40 +08:00
世界
cc7b5d8280 Unwrap 4in6 address received by client packet conn 2023-06-19 13:29:41 +08:00
世界
702d96a738 platform: Improve local DNS transport 2023-06-18 10:00:32 +08:00
世界
b9f34f1309 documentation: Update changelog 2023-06-17 12:21:18 +08:00
世界
07724a0ddd Update dependencies 2023-06-17 12:17:43 +08:00
世界
83c3454685 Update quic-go 2023-06-15 14:52:25 +08:00
世界
7d263eb733 documentation: Update changelog 2023-06-14 16:28:44 +08:00
世界
222687d9c5 Update goreleaser usage 2023-06-14 09:39:13 +08:00
世界
07d3652e30 Build with_dhcp by default 2023-06-14 09:14:45 +08:00
世界
8d5b9d240a Fix outbound start sequence 2023-06-13 22:38:05 +08:00
世界
4f12eba944 Fix hysteria outbound 2023-06-13 21:41:33 +08:00
世界
a7f77d59c1 Update documentation 2023-06-11 22:38:37 +08:00
shadow750d6
597248130f Reconnect once if hysteria request fails
This allows graceful recovery when network isn't good enough.

[Original hysteria source
code](13d46da998/core/cs/client.go (L182))
has similar mechanism.
2023-06-11 22:21:32 +08:00
世界
3c2c9cf317 Migrate gVisor to fork 2023-06-11 22:07:36 +08:00
世界
e572b9d0cd Update dependencies 2023-06-11 20:58:59 +08:00
世界
52e9059a8d Fix fakeip routing 2023-06-11 20:58:59 +08:00
世界
0cb9cff690 Fix shadowsocks none client 2023-06-08 10:23:39 +08:00
世界
c0669cb2a5 Fix TLS 1.2 support for shadow-tls client 2023-06-07 21:03:39 +08:00
世界
c5902f2473 Fix using v2ray websocket transport with detour 2023-06-07 21:03:21 +08:00
世界
22028602e8 Improve read waiter interface 2023-06-07 21:03:11 +08:00
世界
bd54608473 Fix DNS outbound 2023-06-07 21:03:08 +08:00
世界
3741394269 Add cache_id option for Clash cache file 2023-06-07 21:03:06 +08:00
世界
6266d2df7e Fix shadowsocks AEAD UDP server 2023-06-07 21:02:52 +08:00
世界
01dfba722a Use API to create windows firewall rule 2023-06-07 21:01:29 +08:00
世界
f8d5f01665 Reimplemented shadowsocks client 2023-06-07 20:57:07 +08:00
Hellojack
ad999d4791 Fix UVariantLen usage 2023-06-07 20:56:57 +08:00
世界
6f1b258501 Improve DNS caching 2023-06-07 20:56:55 +08:00
世界
f949ddc0ab Set TCP keepalive for WireGuard gVisor TCP connections 2023-06-07 20:53:26 +08:00
Weltolk
f53007cbf3 documentation: Fix fakeip link broken 2023-06-07 20:53:13 +08:00
世界
c287731df9 Improve direct copy 2023-06-07 20:53:00 +08:00
世界
bc32c78d03 Improve multiplex 2023-06-07 20:46:34 +08:00
世界
daee0db7bb clash-api: Reset outbounds in DELETE /connections 2023-06-07 20:45:39 +08:00
世界
91fbf4c79b Add multiplexer for VLESS outbound 2023-06-07 20:45:39 +08:00
世界
54d9ef2f2a Update gVisor to 20230417.0 2023-06-07 20:45:25 +08:00
世界
e056d4502b Add debug http server 2023-06-07 20:45:25 +08:00
世界
98c2c439aa Add filemanager api 2023-06-07 20:45:25 +08:00
世界
b6068cea6b Update wireguard-go 2023-06-07 20:38:30 +08:00
世界
9c9affa719 Ignore system tun stack bind interface error 2023-06-07 20:35:02 +08:00
世界
8eb7dd0059 Improve VLESS request 2023-06-07 20:35:00 +08:00
世界
a62ad44883 Add deadline interface 2023-06-07 20:34:56 +08:00
世界
2850354070 shadowsocks: Multi-user support for legacy AEAD inbound
Signed-off-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-07 20:34:54 +08:00
世界
0a4abcbbc8 Add headers option for HTTP outbound 2023-06-07 20:34:52 +08:00
世界
b491c350ae URLTest improvements 2023-06-07 20:33:56 +08:00
世界
1fbe7c54bf Fix wireguard reconnect 2023-06-07 20:33:53 +08:00
世界
9d32fc9bd1 Use HTTPS URLTest source 2023-06-07 20:33:50 +08:00
世界
542612129d clash-api: Add Clash.Meta APIs 2023-06-07 20:33:41 +08:00
世界
750f87bb0a clash api: download clash-dashboard if external-ui directory is empty 2023-06-07 20:33:06 +08:00
世界
e168de79c7 Add multi-peer support for wireguard outbound 2023-06-07 20:33:04 +08:00
世界
9bca5a517f Add fakeip support 2023-06-07 20:31:26 +08:00
世界
aa94cfb876 Refactor rules 2023-06-07 20:28:21 +08:00
世界
52b776b561 Add dns reverse mapping 2023-06-07 20:19:46 +08:00
世界
c74d3a53d4 documentation: Update changelog 2023-05-19 15:48:35 +08:00
世界
fe7ac80a6c Update dependencies 2023-05-19 15:48:35 +08:00
世界
e50b334b9a Fix uTLS ALPN 2023-05-19 15:41:18 +08:00
XYenon
a0d8e374fb Fix incorrect use of sort.Slice 2023-05-19 15:40:48 +08:00
Larvan2
d3a67cb5ae Enable mkdocs search in documentation
Signed-off-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
2023-05-19 15:40:42 +08:00
世界
e69e98b185 Fix documentation 2023-05-19 15:40:13 +08:00
世界
5e1499d67b Update badtls 2023-05-19 15:39:40 +08:00
世界
e8dad1afeb Fix grpc request 2023-04-22 19:51:04 +08:00
世界
6ce4e31fc8 documentation: Update changelog 2023-04-22 08:26:09 +08:00
世界
d2d4faf520 Revert LRU cache changes 2023-04-22 08:23:49 +08:00
世界
438de36749 Make v2ray http2 conn public 2023-04-22 08:14:55 +08:00
世界
df0eef770e Fix http response check
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-22 08:14:55 +08:00
世界
bbdd495ed5 Fix v2ray-plugin TLS server name
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-21 17:48:36 +08:00
世界
d686172854 Fix grpc lite request host
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-21 17:48:36 +08:00
H1JK
e1d96cb64e Add BaseContext to http servers 2023-04-21 17:48:29 +08:00
H1JK
d5f94b65b7 Fix gRPC service name escape 2023-04-21 17:48:29 +08:00
Hellojack
ec2d0b6b3c Remove TLS requirement for gRPC client 2023-04-21 17:48:29 +08:00
世界
3a92bf993d platform: Add UsePlatformAutoDetectInterfaceControl 2023-04-21 17:47:17 +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
世界
05ed88aba8 Update documentation 2022-11-25 22:59:30 +08:00
世界
9f5cc0442b Fix dockerfile 2022-11-25 22:59:30 +08:00
世界
2641a43ad8 Remove test on pull request 2022-11-25 21:12:45 +08:00
世界
4a6ab5e9fd Fix cancel on start 2022-11-25 21:12:45 +08:00
世界
d1fe17a4db Fix listen packet on address 2022-11-25 21:12:45 +08:00
世界
7c910165ef Cleanup code 2022-11-24 12:37:29 +08:00
世界
8c1fddcf8d Remove connect packet conn 2022-11-24 12:01:25 +08:00
Hellojack
01b4769852 Cleanup gun conn code 2022-11-23 14:56:31 +08:00
世界
a401828ed5 Fix shadowtls server detection 2022-11-22 22:15:38 +08:00
世界
ffd54eef6c Update documentation 2022-11-21 21:20:44 +08:00
世界
c16e4316d6 Fix shadowtls server 2022-11-21 21:20:44 +08:00
世界
8b7fe20b7f Include uTLS in release 2022-11-21 15:25:49 +08:00
世界
696c1065b6 Update stable documentation 2022-11-21 14:57:22 +08:00
世界
5d690f4147 Update documentation 2022-11-21 13:18:04 +08:00
世界
f906641a82 Add uTLS to makefile default tags 2022-11-21 13:18:04 +08:00
世界
89913dfa8c Improve shadowtls server 2022-11-21 13:18:04 +08:00
世界
468778f67f Update dependencies 2022-11-21 13:18:04 +08:00
世界
22a22aebe2 Fix default dns transport strategy 2022-11-21 13:18:04 +08:00
世界
a2d2ec9b45 Update documentation 2022-11-15 17:36:42 +08:00
世界
2695b3516e Update issue template 2022-11-13 11:45:24 +08:00
世界
3a9ef8fac0 Remove unused 2022-11-13 11:30:48 +08:00
世界
ebad363201 Fix create TLS config 2022-11-13 11:24:37 +08:00
世界
11076d52cd Fix dns buffer & quic retry 2022-11-13 11:16:10 +08:00
世界
5eb132063e Fix connect packet connection for mux client 2022-11-12 03:53:42 +08:00
世界
13ab5d3348 Remove follow in update script 2022-11-11 22:32:24 +08:00
世界
ce1ddc400f Support x/h2 v0.2.0 deadline 2022-11-11 22:08:20 +08:00
arm64v8a
2c9d25e853 Fix websocket alpn 2022-11-11 20:01:49 +08:00
世界
3d76777760 Fix tor geoip 2022-11-10 22:42:05 +08:00
世界
24f4dfea04 Fix hysteria test 2022-11-10 21:10:18 +08:00
世界
2fc1a0a9dd Update documentation 2022-11-10 16:33:10 +08:00
世界
617aba84e4 Add multi user support for hysteria inbound 2022-11-09 21:00:08 +08:00
世界
5510c474c7 Fix h2c transport 2022-11-09 12:15:14 +08:00
世界
eb2e8a0b40 Add custom tls client support for std grpc 2022-11-09 11:46:29 +08:00
世界
972491c19d Fix default local DNS server behavior 2022-11-09 10:35:16 +08:00
世界
7358ca4a52 Fix vmess request buffer 2022-11-09 10:16:22 +08:00
世界
61c274045a Update install go script 2022-11-08 23:19:53 +08:00
世界
f205140b04 Fix smux keep alive 2022-11-08 16:45:38 +08:00
世界
1db8e03c86 Fix format 2022-11-08 14:54:19 +08:00
世界
2ecf86c2bc Update patched quic-go 2022-11-08 13:54:01 +08:00
世界
999a847e86 Add custom wireguard worker size option 2022-11-08 13:48:14 +08:00
世界
1f63ce5dee Fix reset outbound 2022-11-06 10:36:19 +08:00
世界
0ad1bbea11 Fix wireguard close 2022-11-06 10:20:23 +08:00
世界
b2cd78d279 Move WFP manipulation to strict route 2022-11-06 10:16:07 +08:00
世界
d5bb58a0b4 Update documentation 2022-11-06 10:16:07 +08:00
世界
7f84936050 Split bind_address 2022-11-06 10:16:07 +08:00
Dreamacro
6adfea0a72 Fix macOS Ventura process name match 2022-11-06 10:16:07 +08:00
Hellojack
10f213bf3d Adjust uTLS wrapper 2022-11-06 10:16:07 +08:00
世界
6e8c4f6576 Update documentation 2022-10-31 13:59:52 +08:00
世界
9779dc0154 Fix test 2022-10-31 13:59:52 +08:00
世界
a2abe31298 Fix uTLS config 2022-10-31 13:59:52 +08:00
世界
930d177dd0 Update dependencies 2022-10-31 13:59:52 +08:00
Fei1Yang
f3d1b59173 Update container action 2022-10-29 18:01:32 +08:00
世界
14452f3049 Update documentation 2022-10-29 18:00:05 +08:00
世界
4119c8647b Update dependencies 2022-10-29 17:56:21 +08:00
世界
90a94a8c63 Improve local dns transport 2022-10-29 17:37:11 +08:00
世界
b0c39ac7ff Suppress no network error 2022-10-28 09:54:04 +08:00
世界
8703e1ff98 Fix decrypt xplus packet 2022-10-28 09:53:57 +08:00
世界
35886b88d7 Add option for custom wireguard reserved bytes 2022-10-28 09:53:57 +08:00
永雏塔菲
d583b35717 Add s390x architecture support
* Update debug.yml

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

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

View File

@@ -1,70 +1,67 @@
name: Bug Report
description: "Create a report to help us improve."
name: Bug report
description: "Report sing-box bug"
body:
- type: checkboxes
id: terms
- type: dropdown
attributes:
label: Welcome
label: Operating system
description: Operating system type
options:
- label: Yes, I'm using the latest major release. Only such installations are supported.
required: true
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true
- label: Yes, I've included all information below (version, config, log, etc).
required: true
- type: textarea
id: problem
attributes:
label: Description of the problem
placeholder: Your problem description
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- Others
validations:
required: true
- type: textarea
id: version
- type: input
attributes:
label: Version of sing-box
value: |-
<details>
```console
$ sing-box version
# Paste output here
```
</details>
label: System version
description: Please provide the operating system version
validations:
required: true
- type: textarea
id: config
- type: dropdown
attributes:
label: Server and client configuration file
value: |-
<details>
```console
# paste json here
```
</details>
label: Installation type
description: Please provide the sing-box installation type
options:
- Original sing-box Command Line
- sing-box for iOS Graphical Client
- sing-box for macOS Graphical Client
- sing-box for Apple tvOS Graphical Client
- sing-box for Android Graphical Client
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
- Third-party graphical clients that advertise themselves as using sing-box (Android)
- Others
validations:
required: true
- type: textarea
id: log
- type: input
attributes:
label: Server and client log file
value: |-
<details>
```console
# paste log here
```
</details>
description: Graphical client version
label: If you are using a graphical client, please provide the version of the client.
- type: textarea
attributes:
label: Version
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
render: shell
- type: textarea
attributes:
label: Description
description: Please provide a detailed description of the error.
validations:
required: true
- type: textarea
attributes:
label: Reproduction
description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.
validations:
required: true
- type: textarea
attributes:
label: Logs
description: |-
If you encounter a crash with the graphical client, please provide crash logs.
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
render: shell

View File

@@ -0,0 +1,81 @@
name: 错误反馈
description: "提交 sing-box 漏洞"
body:
- type: dropdown
attributes:
label: 操作系统
description: 请提供操作系统类型
options:
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- 其他
validations:
required: true
- type: input
attributes:
label: 系统版本
description: 请提供操作系统版本
validations:
required: true
- type: dropdown
attributes:
label: 安装类型
description: 请提供该 sing-box 安装类型
options:
- sing-box 原始命令行程序
- sing-box for iOS 图形客户端程序
- sing-box for macOS 图形客户端程序
- sing-box for Apple tvOS 图形客户端程序
- sing-box for Android 图形客户端程序
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
- 其他
validations:
required: true
- type: input
attributes:
description: 图形客户端版本
label: 如果您使用图形客户端程序,请提供该程序版本。
- type: textarea
attributes:
label: 版本
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
render: shell
- type: textarea
attributes:
label: 描述
description: 请提供错误的详细描述。
validations:
required: true
- type: textarea
attributes:
label: 重现方式
description: 请提供重现错误的步骤,必须包括可以在本地(不依赖与远程服务器)使用 sing-box 原始命令行程序重现错误的配置文件与流程。
validations:
required: true
- type: textarea
attributes:
label: 日志
description: |-
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
render: shell
- type: checkboxes
attributes:
label: 完整性要求
description: 我保证我提供了完整的可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件,否则该 issue 将被关闭。
options:
- label: 我保证
required: true
- type: checkboxes
attributes:
label: 负责性要求
description: 我保证我阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值,否则该 issue 将被关闭。
options:
- label: 我保证
required: true

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

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
name: Generate Documents
on:
push:
branches:
- dev
paths:
- docs/**
- .github/workflows/mkdocs.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-static-i18n
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

View File

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

10
.gitignore vendored
View File

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

View File

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

View File

@@ -1,6 +1,7 @@
project_name: sing-box
builds:
- main: ./cmd/sing-box
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
@@ -9,21 +10,26 @@ builds:
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -X github.com/sagernet/sing-box/constant.Commit={{ .ShortCommit }} -s -w -buildid=
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- android_arm64
- android_amd64
- android_amd64_v3
- linux_386
- linux_amd64_v1
- linux_amd64_v3
- linux_arm64
- linux_arm_7
- linux_s390x
- windows_amd64_v1
- windows_amd64_v3
- windows_386
@@ -32,10 +38,95 @@ builds:
- darwin_amd64_v3
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
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_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=0
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
targets:
- windows_amd64_v1
- windows_386
- darwin_amd64_v1
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_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=1
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives:
- id: archive
builds:
- main
- android
format: tar.gz
format_overrides:
- goos: windows
@@ -44,6 +135,17 @@ archives:
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
builds:
- legacy
format: tar.gz
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
nfpms:
- id: package
package_name: sing-box
@@ -56,6 +158,7 @@ nfpms:
formats:
- deb
- rpm
- archlinux
priority: extra
contents:
- src: release/config/config.json

View File

@@ -1,23 +1,27 @@
FROM golang:1.19-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
ARG TARGETOS TARGETARCH
ARG GOPROXY=""
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
ENV GOOS=$TARGETOS
ENV GOARCH=$TARGETARCH
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags 'no_gvisor,with_quic,with_wireguard,with_acme' \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
-o /go/bin/sing-box \
-ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]
ENTRYPOINT ["sing-box"]

View File

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

177
Makefile
View File

@@ -1,27 +1,43 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_quic,with_wireguard,with_clash_api
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags \
'-X "github.com/sagernet/sing-box/constant.Commit=$(COMMIT)" \
-w -s -buildid='
MAIN = ./cmd/sing-box
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_GO120 = with_quic,with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
.PHONY: test release
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 -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release docs
build:
go build $(MAIN_PARAMS) $(MAIN)
ci_build_go118:
go build $(PARAMS) $(MAIN)
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
ci_build:
go build $(PARAMS) $(MAIN)
go build $(MAIN_PARAMS) $(MAIN)
install:
go install $(PARAMS) $(MAIN)
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write -s "standard,prefix(github.com/sagernet/),default" .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@v0.4.0
go install -v github.com/daixiang0/gci@latest
lint:
GOOS=linux golangci-lint run ./...
@@ -42,33 +58,140 @@ proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot:
goreleaser release --rm-dist --snapshot
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist
release:
goreleaser release --rm-dist --skip-publish
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
rm -r dist
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/*.pkg.tar.zst dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
rm -r dist/release
release_install:
go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest
test:
@go test -v . && \
pushd test && \
go mod tidy && \
go test -v -tags with_quic,with_wireguard,with_grpc . && \
popd
update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
publish_android_appcenter:
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
build_ios:
cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \
xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive
upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
release_ios: build_ios upload_ios_app_store
build_macos:
cd ../sing-box-for-apple && \
rm -rf build/SFM.xcarchive && \
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive
upload_macos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
release_macos: build_macos upload_macos_app_store
build_macos_independent:
cd ../sing-box-for-apple && \
rm -rf build/SFT.System.xcarchive && \
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive
notarize_macos_independent:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
wait_notarize_macos_independent:
sleep 60
export_macos_independent:
rm -rf dist/SFM
mkdir -p dist/SFM
cd ../sing-box-for-apple && \
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
upload_macos_independent:
cd dist/SFM && \
rm -f *.zip && \
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
ghr --replace --draft --prerelease "v${VERSION}" *.zip
release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
upload_tvos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
release_apple_beta: update_apple_version release_ios release_macos release_tvos
test:
@go test -v ./... && \
cd test && \
go mod tidy && \
go test -v -tags "$(TAGS_TEST)" .
test_stdio:
@go test -v ./... && \
cd test && \
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_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-20230915142329-c6740b6d2950
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
docs:
mkdocs serve
publish_docs:
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
docs_install:
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box
update:

View File

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

104
adapter/conn_router.go Normal file
View File

@@ -0,0 +1,104 @@
package adapter
import (
"context"
"net"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -4,31 +4,61 @@ import (
"context"
"net"
"github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network"
)
type ClashServer interface {
Service
TrafficController
PreStarter
Mode() string
ModeList() []string
StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
}
type ClashCacheFile interface {
LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error
FakeIPStorage
}
type Tracker interface {
Leave()
}
type TrafficController interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
}
type OutboundGroup interface {
Outbound
Now() string
All() []string
}
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now()
}
return detour.Tag()
}
type V2RayServer interface {
Service
StatsService() V2RayStatsService
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}

32
adapter/fakeip.go Normal file
View File

@@ -0,0 +1,32 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/logger"
)
type FakeIPStore interface {
Service
Contains(address netip.Addr) bool
Create(domain string, isIPv6 bool) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)
Reset() error
}
type FakeIPStorage interface {
FakeIPMetadata() *FakeIPMetadata
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
FakeIPSaveMetadataAsync(metadata *FakeIPMetadata)
FakeIPStore(address netip.Addr, domain string) error
FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger)
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool)
FakeIPReset() error
}
type FakeIPTransport interface {
dns.Transport
Store() FakeIPStore
}

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

@@ -6,7 +6,7 @@ import (
"net/netip"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -27,7 +27,7 @@ type InjectableInbound interface {
type InboundContext struct {
Inbound string
InboundType string
IPVersion int
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr
@@ -38,16 +38,19 @@ type InboundContext struct {
// cache
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
DomainStrategy dns.DomainStrategy
SniffEnabled bool
SniffOverrideDestination bool
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
InboundOptions option.InboundOptions
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
FakeIP bool
// dns cache
QueryType uint16
}
type inboundContextKey struct{}
@@ -72,3 +75,11 @@ func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
metadata = new(InboundContext)
return WithContext(ctx, metadata), metadata
}
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
var newMetadata InboundContext
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata = *metadata
}
return WithContext(ctx, &newMetadata), &newMetadata
}

View File

@@ -13,6 +13,7 @@ type Outbound interface {
Type() string
Tag() string
Network() []string
Dependencies() []string
N.Dialer
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error

9
adapter/prestart.go Normal file
View File

@@ -0,0 +1,9 @@
package adapter
type PreStarter interface {
PreStart() error
}
type PostStarter interface {
PostStart() error
}

View File

@@ -2,44 +2,64 @@ package adapter
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"golang.org/x/net/dns/dnsmessage"
mdns "github.com/miekg/dns"
)
type Router interface {
Service
PostStarter
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
FakeIPStore() FakeIPStore
ConnectionRouter
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
ClearDNSCache()
InterfaceBindManager() control.BindManager
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
WIFIState() WIFIState
Rules() []Rule
SetTrafficController(controller TrafficController)
ClashServer() ClashServer
SetClashServer(server ClashServer)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
ResetNetwork() error
}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return service.ContextWith(ctx, router)
}
func RouterFromContext(ctx context.Context) Router {
return service.FromContext[Router](ctx)
}
type Rule interface {
@@ -54,4 +74,14 @@ type Rule interface {
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}

8
adapter/time.go Normal file
View File

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

View File

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

View File

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

331
box.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -18,83 +19,72 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
clashServer adapter.ClashServer
done chan struct{}
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
preServices map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
func New(ctx context.Context, options option.Options) (*Box, error) {
createdAt := time.Now()
logOptions := common.PtrValueOrDefault(options.Log)
type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
PlatformLogWriter log.PlatformWriter
}
func New(options Options) (*Box, error) {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.ContextWithDefaultManager(ctx)
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
var needV2RayAPI bool
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
needClashAPI = true
}
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
var logWriter io.Writer
switch logOptions.Output {
case "", "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
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)
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
var defaultLogWriter io.Writer
if options.PlatformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
Context: ctx,
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformLogWriter,
})
if err != nil {
return nil, E.Cause(err, "create log factory")
}
router, err := route.NewRouter(
ctx,
logFactory.NewLogger("router"),
logFactory.NewLogger("dns"),
logFactory,
common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
@@ -114,6 +104,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -132,6 +123,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
ctx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
@@ -139,7 +131,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
outbounds = append(outbounds, out)
}
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)
outbounds = append(outbounds, out)
return out
@@ -147,28 +139,64 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
if err != nil {
return nil, err
}
var clashServer adapter.ClashServer
if options.PlatformInterface != nil {
err = options.PlatformInterface.Initialize(ctx, router)
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 {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
router.SetTrafficController(clashServer)
router.SetClashServer(clashServer)
preServices["clash api"] = clashServer
}
if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
router.SetV2RayServer(v2rayServer)
preServices["v2ray api"] = v2rayServer
}
return &Box{
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.NewLogger(""),
logFile: logFile,
clashServer: clashServer,
done: make(chan struct{}),
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
preServices: preServices,
postServices: postServices,
done: make(chan struct{}),
}, nil
}
func (s *Box) PreStart() error {
err := s.preStart()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) Start() error {
err := s.start()
if err != nil {
@@ -182,51 +210,78 @@ func (s *Box) Start() error {
}
}()
s.Close()
}
return err
}
func (s *Box) start() error {
for i, out := range s.outbounds {
if starter, isStarter := out.(common.Starter); isStarter {
err := starter.Start()
if err != nil {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
}
}
}
err := s.router.Start()
if err != nil {
return err
}
for i, in := range s.inbounds {
err = in.Start()
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, "]")
}
}
if s.clashServer != nil {
err = s.clashServer.Start()
if err != nil {
return E.Cause(err, "start clash api server")
}
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
if preService, isPreService := service.(adapter.PreStarter); isPreService {
s.logger.Trace("pre-start ", serviceName)
err := preService.PreStart()
if err != nil {
return E.Cause(err, "pre-starting ", serviceName)
}
}
}
err := s.startOutbounds()
if err != nil {
return err
}
return s.router.Start()
}
func (s *Box) start() error {
err := s.preStart()
if err != nil {
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 {
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()
if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
return s.postStart()
}
func (s *Box) postStart() error {
for serviceName, service := range s.postServices {
s.logger.Trace("starting ", service)
err := service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for _, outbound := range s.outbounds {
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
s.logger.Trace("post-starting outbound/", outbound.Tag())
err := lateOutbound.PostStart()
if err != nil {
return E.Cause(err, "post-start outbound/", outbound.Tag())
}
}
}
s.logger.Trace("post-starting router")
return s.router.PostStart()
}
func (s *Box) Close() error {
select {
case <-s.done:
@@ -234,16 +289,46 @@ func (s *Box) Close() error {
default:
close(s.done)
}
for _, in := range s.inbounds {
in.Close()
var errors error
for serviceName, service := range s.postServices {
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 {
common.Close(out)
for i, in := range s.inbounds {
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(
s.router,
s.logFactory,
s.clashServer,
common.PtrOrNil(s.logFile),
)
for i, out := range s.outbounds {
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
})
}
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 {
return s.router
}

79
box_outbound.go Normal file
View File

@@ -0,0 +1,79 @@
package box
import (
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func (s *Box) startOutbounds() error {
outboundTags := make(map[adapter.Outbound]string)
outbounds := make(map[string]adapter.Outbound)
for i, outboundToStart := range s.outbounds {
var outboundTag string
if outboundToStart.Tag() == "" {
outboundTag = F.ToString(i)
} else {
outboundTag = outboundToStart.Tag()
}
if _, exists := outbounds[outboundTag]; exists {
return E.New("outbound tag ", outboundTag, " duplicated")
}
outboundTags[outboundToStart] = outboundTag
outbounds[outboundTag] = outboundToStart
}
started := make(map[string]bool)
for {
canContinue := false
startOne:
for _, outboundToStart := range s.outbounds {
outboundTag := outboundTags[outboundToStart]
if started[outboundTag] {
continue
}
dependencies := outboundToStart.Dependencies()
for _, dependency := range dependencies {
if !started[dependency] {
continue startOne
}
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
if err != nil {
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
}
}
if len(started) == len(s.outbounds) {
break
}
if canContinue {
continue
}
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
return !started[outboundTags[it]]
})
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
return !started[it]
})
if common.Contains(oTree, problemOutboundTag) {
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
}
problemOutbound := outbounds[problemOutboundTag]
if problemOutbound == nil {
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
}
return nil
}

View File

@@ -0,0 +1,26 @@
package main
import (
"go/build"
"os"
"os/exec"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
)
func main() {
build_shared.FindSDK()
if os.Getenv("GOPATH") == "" {
os.Setenv("GOPATH", build.Default.GOPATH)
}
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,144 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"strings"
_ "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
sharedTags []string
iosTags []string
debugTags []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)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
debugTags = append(debugTags, "debug")
}
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, strings.Join(sharedTags, ","))
} else {
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
}
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,tvos,tvossimulator,macos",
"-libname=box",
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
tags := append(sharedTags, iosTags...)
args = append(args, "-tags")
if !debugEnabled {
args = append(args, strings.Join(tags, ","))
} else {
args = append(args, strings.Join(append(tags, debugTags...), ","))
}
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-apple")
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,33 @@
package build_shared
import (
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing/common"
"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()
version := badversion.Parse(currentTagRev[1:])
return version.String() + "-" + shortCommit, nil
}
func ReadTagVersion() (badversion.Version, error) {
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
version := badversion.Parse(currentTagRev[1:])
if currentTagRev != currentTag {
if version.PreReleaseIdentifier == "" {
version.Patch++
}
}
return version, 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

@@ -0,0 +1,51 @@
package main
import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
)
func main() {
newVersion := common.Must1(build_shared.ReadTagVersion())
androidPath, err := filepath.Abs("../sing-box-for-android")
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("local.properties"))
var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
for _, propPair := range propsList {
if propPair[0] == "VERSION_NAME" {
if propPair[1] == newVersion.String() {
log.Info("version not changed")
return
}
propPair[1] = newVersion.String()
log.Info("updated version to ", newVersion.String())
}
}
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_CODE":
versionCode := common.Must1(strconv.ParseInt(propPair[1], 10, 64))
propPair[1] = strconv.Itoa(int(versionCode + 1))
log.Info("updated version code to ", propPair[1])
case "RELEASE_NOTES":
propPair[1] = "sing-box " + newVersion.String()
}
}
var newProps []string
for _, propPair := range propsList {
newProps = append(newProps, strings.Join(propPair, "="))
}
common.Must(os.WriteFile("local.properties", []byte(strings.Join(newProps, "\n")), 0o644))
}

View File

@@ -0,0 +1,131 @@
package main
import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"howett.net/plist"
)
func main() {
newVersion := common.Must1(build_shared.ReadTagVersion())
applePath, err := filepath.Abs("../sing-box-for-apple")
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(applePath))
projectFile := common.Must1(os.Open("sing-box.xcodeproj/project.pbxproj"))
var project map[string]any
decoder := plist.NewDecoder(projectFile)
common.Must(decoder.Decode(&project))
objectsMap := project["objects"].(map[string]any)
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
}
var updated2 bool
if macProjectVersion := os.Getenv("MACOS_PROJECT_VERSION"); macProjectVersion != "" {
newContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{"SFM"}, macProjectVersion)
if updated2 {
log.Info("updated macos project version to ", macProjectVersion)
}
}
if updated0 || updated1 || updated2 {
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
}
}
func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDList []string, newVersion string) (string, bool) {
objectKeyList := findObjectKey(objectsMap, bundleIDList)
var updated bool
for _, objectKey := range objectKeyList {
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
indexes := matchRegexp.FindStringIndex(projectContent)
if len(indexes) < 2 {
println(projectContent)
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
}
indexStart := indexes[1]
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := projectContent[versionStart:versionEnd]
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
}
return projectContent, updated
}
func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent string, directoryList []string, newVersion string) (string, bool) {
objectKeyList := findObjectKeyByDirectory(objectsMap, directoryList)
var updated bool
for _, objectKey := range objectKeyList {
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
indexes := matchRegexp.FindStringIndex(projectContent)
if len(indexes) < 2 {
println(projectContent)
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
}
indexStart := indexes[1]
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "CURRENT_PROJECT_VERSION = ") + 26
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := projectContent[versionStart:versionEnd]
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
}
return projectContent, updated
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
if buildSettings == nil {
continue
}
bundleIDObject := buildSettings.(map[string]any)["PRODUCT_BUNDLE_IDENTIFIER"]
if bundleIDObject == nil {
continue
}
if common.Contains(bundleIDList, bundleIDObject.(string)) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
if buildSettings == nil {
continue
}
infoPListFile := buildSettings.(map[string]any)["INFOPLIST_FILE"]
if infoPListFile == nil {
continue
}
for _, searchDirectory := range directoryList {
if strings.HasPrefix(infoPListFile.(string), searchDirectory+"/") {
objectKeyList = append(objectKeyList, objectKey)
}
}
}
return objectKeyList
}

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
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"
)
var commandGenerate = &cobra.Command{
Use: "generate",
Short: "Generate things",
}
func init() {
commandGenerate.AddCommand(commandGenerateUUID)
commandGenerate.AddCommand(commandGenerateRandom)
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
}

View File

@@ -0,0 +1,39 @@
package main
import (
"os"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var pqSignatureSchemesEnabled bool
var commandGenerateECHKeyPair = &cobra.Command{
Use: "ech-keypair <plain_server_name>",
Short: "Generate TLS ECH key pair",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateECHKeyPair(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes")
commandGenerate.AddCommand(commandGenerateECHKeyPair)
}
func generateECHKeyPair(serverName string) error {
configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled)
if err != nil {
return err
}
os.Stdout.WriteString(configPem)
os.Stdout.WriteString(keyPem)
return nil
}

View File

@@ -0,0 +1,40 @@
package main
import (
"os"
"time"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var flagGenerateTLSKeyPairMonths int
var commandGenerateTLSKeyPair = &cobra.Command{
Use: "tls-keypair <server_name>",
Short: "Generate TLS self sign key pair",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateTLSKeyPair(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months")
commandGenerate.AddCommand(commandGenerateTLSKeyPair)
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}
os.Stdout.WriteString(string(privateKeyPem) + "\n")
os.Stdout.WriteString(string(publicKeyPem) + "\n")
return nil
}

View File

@@ -0,0 +1,40 @@
//go:build go1.20
package main
import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGenerateVAPIDKeyPair = &cobra.Command{
Use: "vapid-keypair",
Short: "Generate VAPID key pair",
Run: func(cmd *cobra.Command, args []string) {
err := generateVAPIDKeyPair()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerate.AddCommand(commandGenerateVAPIDKeyPair)
}
func generateVAPIDKeyPair() error {
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n")
return nil
}

View File

@@ -0,0 +1,61 @@
package main
import (
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func init() {
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
}
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
}

174
cmd/sing-box/cmd_merge.go Normal file
View File

@@ -0,0 +1,174 @@
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var commandMerge = &cobra.Command{
Use: "merge [output]",
Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
mainCommand.AddCommand(commandMerge)
}
func merge(outputPath string) error {
mergedOptions, err := readConfigAndMerge()
if err != nil {
return err
}
err = mergePathResources(&mergedOptions)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(mergedOptions)
if err != nil {
return E.Cause(err, "encode config")
}
if existsContent, err := os.ReadFile(outputPath); err != nil {
if string(existsContent) == buffer.String() {
return nil
}
}
err = rw.WriteFile(outputPath, buffer.Bytes())
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}
func mergePathResources(options *option.Options) error {
for index, inbound := range options.Inbounds {
switch inbound.Type {
case C.TypeHTTP:
inbound.HTTPOptions.TLS = mergeTLSInboundOptions(inbound.HTTPOptions.TLS)
case C.TypeMixed:
inbound.MixedOptions.TLS = mergeTLSInboundOptions(inbound.MixedOptions.TLS)
case C.TypeVMess:
inbound.VMessOptions.TLS = mergeTLSInboundOptions(inbound.VMessOptions.TLS)
case C.TypeTrojan:
inbound.TrojanOptions.TLS = mergeTLSInboundOptions(inbound.TrojanOptions.TLS)
case C.TypeNaive:
inbound.NaiveOptions.TLS = mergeTLSInboundOptions(inbound.NaiveOptions.TLS)
case C.TypeHysteria:
inbound.HysteriaOptions.TLS = mergeTLSInboundOptions(inbound.HysteriaOptions.TLS)
case C.TypeVLESS:
inbound.VLESSOptions.TLS = mergeTLSInboundOptions(inbound.VLESSOptions.TLS)
case C.TypeTUIC:
inbound.TUICOptions.TLS = mergeTLSInboundOptions(inbound.TUICOptions.TLS)
case C.TypeHysteria2:
inbound.Hysteria2Options.TLS = mergeTLSInboundOptions(inbound.Hysteria2Options.TLS)
default:
continue
}
options.Inbounds[index] = inbound
}
for index, outbound := range options.Outbounds {
switch outbound.Type {
case C.TypeHTTP:
outbound.HTTPOptions.TLS = mergeTLSOutboundOptions(outbound.HTTPOptions.TLS)
case C.TypeVMess:
outbound.VMessOptions.TLS = mergeTLSOutboundOptions(outbound.VMessOptions.TLS)
case C.TypeTrojan:
outbound.TrojanOptions.TLS = mergeTLSOutboundOptions(outbound.TrojanOptions.TLS)
case C.TypeHysteria:
outbound.HysteriaOptions.TLS = mergeTLSOutboundOptions(outbound.HysteriaOptions.TLS)
case C.TypeSSH:
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
case C.TypeVLESS:
outbound.VLESSOptions.TLS = mergeTLSOutboundOptions(outbound.VLESSOptions.TLS)
case C.TypeTUIC:
outbound.TUICOptions.TLS = mergeTLSOutboundOptions(outbound.TUICOptions.TLS)
case C.TypeHysteria2:
outbound.Hysteria2Options.TLS = mergeTLSOutboundOptions(outbound.Hysteria2Options.TLS)
default:
continue
}
options.Outbounds[index] = outbound
}
return nil
}
func mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTLSOptions {
if options == nil {
return nil
}
if options.CertificatePath != "" {
if content, err := os.ReadFile(options.CertificatePath); err == nil {
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.KeyPath != "" {
if content, err := os.ReadFile(options.KeyPath); err == nil {
options.Key = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.ECH != nil {
if options.ECH.KeyPath != "" {
if content, err := os.ReadFile(options.ECH.KeyPath); err == nil {
options.ECH.Key = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
return options
}
func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.OutboundTLSOptions {
if options == nil {
return nil
}
if options.CertificatePath != "" {
if content, err := os.ReadFile(options.CertificatePath); err == nil {
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.ECH != nil {
if options.ECH.ConfigPath != "" {
if content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {
options.ECH.Config = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
return options
}
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
}
}
return options
}
func trimStringArray(array []string) []string {
return common.Filter(array, func(it string) bool {
return strings.TrimSpace(it) != ""
})
}

View File

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

@@ -3,9 +3,9 @@ package main
import (
"os"
"runtime"
"runtime/debug"
C "github.com/sagernet/sing-box/constant"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
@@ -25,30 +25,40 @@ func init() {
}
func printVersion(cmd *cobra.Command, args []string) {
var version string
if !nameOnly {
version = "sing-box "
if nameOnly {
os.Stdout.WriteString(C.Version + "\n")
return
}
version += F.ToString(C.Version)
if C.Commit != "" {
version += "." + C.Commit
}
if !nameOnly {
version += " ("
version += runtime.Version()
version += ", "
version += runtime.GOOS
version += "/"
version += runtime.GOARCH
version += ", "
version += "CGO "
if C.CGO_ENABLED {
version += "enabled"
} else {
version += "disabled"
version := "sing-box version " + C.Version + "\n\n"
version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n"
var tags string
var revision string
debugInfo, loaded := debug.ReadBuildInfo()
if loaded {
for _, setting := range debugInfo.Settings {
switch setting.Key {
case "-tags":
tags = setting.Value
case "vcs.revision":
revision = setting.Value
}
}
version += ")"
}
version += "\n"
if tags != "" {
version += "Tags: " + tags + "\n"
}
if revision != "" {
version += "Revision: " + revision + "\n"
}
if C.CGO_ENABLED {
version += "CGO: enabled\n"
} else {
version += "CGO: disabled\n"
}
os.Stdout.WriteString(version)
}

View File

@@ -1,44 +0,0 @@
//go:build debug
package main
import (
"encoding/json"
"net/http"
_ "net/http/pprof"
"runtime"
"runtime/debug"
"github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing-box/log"
"github.com/dustin/go-humanize"
)
func init() {
http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
go debug.FreeOSMemory()
})
http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.Bytes(memStats.HeapInuse))
memObject.Put("stack", humanize.Bytes(memStats.StackInuse))
memObject.Put("idle", humanize.Bytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())
encoder := json.NewEncoder(writer)
encoder.SetIndent("", " ")
encoder.Encode(memObject)
})
go func() {
err := http.ListenAndServe("0.0.0.0:8964", nil)
if err != nil {
log.Debug(err)
}
}()
}

View File

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

View File

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

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

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

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

View File

@@ -0,0 +1,14 @@
//go:build !go1.19 || go1.21
package badtls
import (
"crypto/tls"
"os"
aTLS "github.com/sagernet/sing/common/tls"
)
func Create(conn *tls.Conn) (aTLS.Conn, error) {
return nil, os.ErrInvalid
}

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

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

View File

@@ -0,0 +1,124 @@
package badversion
import (
"strconv"
"strings"
F "github.com/sagernet/sing/common/format"
)
type Version struct {
Major int
Minor int
Patch int
Commit string
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 == anotherVersion.PreReleaseIdentifier {
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
return true
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
return false
}
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
return true
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
return false
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
}
return false
}
func (v Version) VersionString() string {
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
}
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.Commit = 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
}

54
common/conntrack/conn.go Normal file
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,35 @@
package conntrack
import (
runtimeDebug "runtime/debug"
"time"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
)
var (
KillerEnabled bool
MemoryLimit uint64
killerLastCheck time.Time
)
func KillerCheck() error {
if !KillerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(killerLastCheck) < 3*time.Second {
return nil
}
killerLastCheck = nowTime
if memory.Total() > MemoryLimit {
Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}

View File

@@ -0,0 +1,55 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/bufio"
"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 bufio.NewPacketConn(c.PacketConn)
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

47
common/conntrack/track.go Normal file
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

@@ -1,87 +0,0 @@
package debugio
import (
"net"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type LogConn struct {
N.ExtendedConn
logger log.Logger
prefix string
}
func NewLogConn(conn net.Conn, logger log.Logger, prefix string) N.ExtendedConn {
return &LogConn{bufio.NewExtendedConn(conn), logger, prefix}
}
func (c *LogConn) Read(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Read(p)
if n > 0 {
c.logger.Debug(c.prefix, " read ", buf.EncodeHexString(p[:n]))
}
return
}
func (c *LogConn) Write(p []byte) (n int, err error) {
c.logger.Debug(c.prefix, " write ", buf.EncodeHexString(p))
return c.ExtendedConn.Write(p)
}
func (c *LogConn) ReadBuffer(buffer *buf.Buffer) error {
err := c.ExtendedConn.ReadBuffer(buffer)
if err == nil {
c.logger.Debug(c.prefix, " read buffer ", buf.EncodeHexString(buffer.Bytes()))
}
return err
}
func (c *LogConn) WriteBuffer(buffer *buf.Buffer) error {
c.logger.Debug(c.prefix, " write buffer ", buf.EncodeHexString(buffer.Bytes()))
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *LogConn) Upstream() any {
return c.ExtendedConn
}
type LogPacketConn struct {
N.NetPacketConn
logger log.Logger
prefix string
}
func NewLogPacketConn(conn net.PacketConn, logger log.Logger, prefix string) N.NetPacketConn {
return &LogPacketConn{bufio.NewPacketConn(conn), logger, prefix}
}
func (c *LogPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.NetPacketConn.ReadFrom(p)
if n > 0 {
c.logger.Debug(c.prefix, " read from ", addr, " ", buf.EncodeHexString(p[:n]))
}
return
}
func (c *LogPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.logger.Debug(c.prefix, " write to ", addr, " ", buf.EncodeHexString(p))
return c.NetPacketConn.WriteTo(p, addr)
}
func (c *LogPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.NetPacketConn.ReadPacket(buffer)
if err == nil {
c.logger.Debug(c.prefix, " read packet from ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
}
return
}
func (c *LogPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
c.logger.Debug(c.prefix, " write packet to ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
return c.NetPacketConn.WritePacket(buffer, destination)
}

View File

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

View File

@@ -1,48 +0,0 @@
package debugio
import (
"net"
"sync"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
)
type RaceConn struct {
N.ExtendedConn
readAccess sync.Mutex
writeAccess sync.Mutex
}
func NewRaceConn(conn net.Conn) N.ExtendedConn {
return &RaceConn{ExtendedConn: bufio.NewExtendedConn(conn)}
}
func (c *RaceConn) Read(p []byte) (n int, err error) {
c.readAccess.Lock()
defer c.readAccess.Unlock()
return c.ExtendedConn.Read(p)
}
func (c *RaceConn) Write(p []byte) (n int, err error) {
c.writeAccess.Lock()
defer c.writeAccess.Unlock()
return c.ExtendedConn.Write(p)
}
func (c *RaceConn) ReadBuffer(buffer *buf.Buffer) error {
c.readAccess.Lock()
defer c.readAccess.Unlock()
return c.ExtendedConn.ReadBuffer(buffer)
}
func (c *RaceConn) WriteBuffer(buffer *buf.Buffer) error {
c.writeAccess.Lock()
defer c.writeAccess.Unlock()
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *RaceConn) Upstream() any {
return c.ExtendedConn
}

View File

@@ -3,92 +3,45 @@ package dialer
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/warning"
"github.com/sagernet/sing-box/common/conntrack"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/database64128/tfo-go"
)
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 {
dialer tfo.Dialer
udpDialer net.Dialer
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
bindUDPAddr string
udpAddr4 string
udpAddr6 string
}
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
warnBindInterfaceOnUnsupportedPlatform.Check()
bindFunc := control.BindToInterface(router.InterfaceBindManager(), options.BindInterface)
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.AutoDetectInterface() {
if C.IsWindows {
bindFunc := control.BindToInterfaceIndexFunc(func(network, address string) int {
return router.InterfaceMonitor().DefaultInterfaceIndex(M.ParseSocksaddr(address).Addr)
})
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func(network, address string) string {
return router.InterfaceMonitor().DefaultInterfaceName(M.ParseSocksaddr(address).Addr)
})
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface())
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
if options.RoutingMark != 0 {
warnRoutingMarkOnUnsupportedPlatform.Check()
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
} else if router.DefaultMark() != 0 {
@@ -96,11 +49,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
}
if options.ReuseAddr {
warnReuseAdderOnUnsupportedPlatform.Check()
listener.Control = control.Append(listener.Control, control.ReuseAddr())
}
if options.ProtectPath != "" {
warnProtectPathOnNonAndroid.Check()
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
}
@@ -109,25 +60,61 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
} else {
dialer.Timeout = C.TCPTimeout
}
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
var udpFragment bool
if options.UDPFragment != nil {
udpFragment = *options.UDPFragment
} else {
udpFragment = options.UDPFragmentDefault
}
var bindUDPAddr string
udpDialer := dialer
var bindAddress netip.Addr
if options.BindAddress != nil {
bindAddress = options.BindAddress.Build()
if !udpFragment {
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
}
if bindAddress.IsValid() {
dialer.LocalAddr = &net.TCPAddr{
IP: bindAddress.AsSlice(),
var (
dialer4 = dialer
udpDialer4 = dialer
udpAddr4 string
)
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build()
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
}
var (
dialer6 = dialer
udpDialer6 = dialer
udpAddr6 string
)
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build()
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
if options.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
udpDialer.LocalAddr = &net.UDPAddr{
IP: bindAddress.AsSlice(),
}
bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).String()
setMultiPathTCP(&dialer4)
}
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, udpDialer, listener, bindUDPAddr}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
}
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
if err != nil {
return nil, err
}
return &DefaultDialer{
tcpDialer4,
tcpDialer6,
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
}, nil
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
@@ -136,11 +123,39 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
}
switch N.NetworkName(network) {
case N.NetworkUDP:
return d.udpDialer.DialContext(ctx, network, address.String())
if !address.IsIPv6() {
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else {
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
}
if !address.IsIPv6() {
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
return d.dialer.DialContext(ctx, network, address.Unwrap().String())
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.bindUDPAddr)
if destination.IsIPv6() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
} else {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
}
}
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

@@ -0,0 +1,15 @@
//go:build go1.20
package dialer
import (
"net"
"github.com/sagernet/tfo-go"
)
type tcpDialer = tfo.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
}

View File

@@ -0,0 +1,11 @@
//go:build go1.21
package dialer
import "net"
const go121Available = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -0,0 +1,18 @@
//go:build !go1.20
package dialer
import (
"net"
E "github.com/sagernet/sing/common/exceptions"
)
type tcpDialer = net.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
if tfoEnabled {
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
}
return dialer, nil
}

View File

@@ -0,0 +1,12 @@
//go:build !go1.21
package dialer
import (
"net"
)
const go121Available = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -6,19 +6,35 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
N "github.com/sagernet/sing/common/network"
)
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
var dialer N.Dialer
func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
return common.Must1(New(router, options))
}
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer = NewDefault(router, options)
dialer, err = NewDefault(router, options)
if err != nil {
return nil, err
}
} else {
dialer = NewDetour(router, options.Detour)
}
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
dialer = NewResolveDialer(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
domainStrategy,
time.Duration(options.FallbackDelay))
}
return dialer
return dialer, nil
}

View File

@@ -9,20 +9,23 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type ResolveDialer struct {
dialer N.Dialer
parallel bool
router adapter.Router
strategy dns.DomainStrategy
fallbackDelay time.Duration
}
func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
return &ResolveDialer{
dialer,
parallel,
router,
strategy,
fallbackDelay,
@@ -33,7 +36,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx, metadata := adapter.AppendContext(ctx)
ctx, metadata := adapter.ExtendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""
@@ -47,14 +50,18 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
if err != nil {
return nil, err
}
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
if d.parallel {
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
} else {
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
}
}
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx, metadata := adapter.AppendContext(ctx)
ctx, metadata := adapter.ExtendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""
@@ -68,11 +75,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil {
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 {
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 {

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
}

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

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

20
common/dialer/tfo_stub.go Normal file
View File

@@ -0,0 +1,20 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.DialContext(ctx, network, destination.String())
default:
return dialer.DialContext(ctx, network, destination.AddrString())
}
}

View File

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

158
common/humanize/bytes.go Normal file
View File

@@ -0,0 +1,158 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var defaultSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
var memorysSizeTable = map[string]uint64{
"b": Byte,
"kb": KiByte,
"mb": MiByte,
"gb": GiByte,
"tb": TiByte,
"pb": PiByte,
"eb": EiByte,
"": Byte,
"k": KiByte,
"m": MiByte,
"g": GiByte,
"t": TiByte,
"p": PiByte,
"e": EiByte,
}
var (
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func Bytes(s uint64) string {
return humanateBytes(s, 1000, defaultSizes)
}
func MemoryBytes(s uint64) string {
return humanateBytes(s, 1024, defaultSizes)
}
func IBytes(s uint64) string {
return humanateBytes(s, 1024, iSizes)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func ParseBytes(s string) (uint64, error) {
return parseBytes0(s, defaultSizeTable)
}
func ParseMemoryBytes(s string) (uint64, error) {
return parseBytes0(s, memorysSizeTable)
}
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := sizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

75
common/interrupt/conn.go Normal file
View File

@@ -0,0 +1,75 @@
package interrupt
import (
"net"
"github.com/sagernet/sing/common/x/list"
)
/*type GroupedConn interface {
MarkAsInternal()
}
func MarkAsInternal(conn any) {
if groupedConn, isGroupConn := common.Cast[GroupedConn](conn); isGroupConn {
groupedConn.MarkAsInternal()
}
}*/
type Conn struct {
net.Conn
group *Group
element *list.Element[*groupConnItem]
}
/*func (c *Conn) MarkAsInternal() {
c.element.Value.internal = true
}*/
func (c *Conn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
return c.Conn.Close()
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}
func (c *Conn) Upstream() any {
return c.Conn
}
type PacketConn struct {
net.PacketConn
group *Group
element *list.Element[*groupConnItem]
}
/*func (c *PacketConn) MarkAsInternal() {
c.element.Value.internal = true
}*/
func (c *PacketConn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
return c.PacketConn.Close()
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}

View File

@@ -0,0 +1,13 @@
package interrupt
import "context"
type contextKeyIsExternalConnection struct{}
func ContextWithIsExternalConnection(ctx context.Context) context.Context {
return context.WithValue(ctx, contextKeyIsExternalConnection{}, true)
}
func IsExternalConnectionFromContext(ctx context.Context) bool {
return ctx.Value(contextKeyIsExternalConnection{}) != nil
}

52
common/interrupt/group.go Normal file
View File

@@ -0,0 +1,52 @@
package interrupt
import (
"io"
"net"
"sync"
"github.com/sagernet/sing/common/x/list"
)
type Group struct {
access sync.Mutex
connections list.List[*groupConnItem]
}
type groupConnItem struct {
conn io.Closer
isExternal bool
}
func NewGroup() *Group {
return &Group{}
}
func (g *Group) NewConn(conn net.Conn, isExternal bool) net.Conn {
g.access.Lock()
defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
return &Conn{Conn: conn, group: g, element: item}
}
func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketConn {
g.access.Lock()
defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
return &PacketConn{PacketConn: conn, group: g, element: item}
}
func (g *Group) Interrupt(interruptExternalConnections bool) {
g.access.Lock()
defer g.access.Unlock()
var toDelete []*list.Element[*groupConnItem]
for element := g.connections.Front(); element != nil; element = element.Next() {
if !element.Value.isExternal || interruptExternalConnections {
element.Value.conn.Close()
toDelete = append(toDelete, element)
}
}
for _, element := range toDelete {
g.connections.Remove(element)
}
}

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

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

View File

@@ -1,500 +1,42 @@
package mux
import (
"context"
"encoding/binary"
"io"
"net"
"sync"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
var _ N.Dialer = (*Client)(nil)
type Client = mux.Client
type Client struct {
access sync.Mutex
connections list.List[abstractSession]
ctx context.Context
dialer N.Dialer
protocol Protocol
maxConnections int
minStreams int
maxStreams int
}
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
return &Client{
ctx: ctx,
dialer: dialer,
protocol: protocol,
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
}
}
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.OutboundMultiplexOptions) (*Client, error) {
if !options.Enabled {
return nil, nil
}
if options.MaxConnections == 0 && options.MaxStreams == 0 {
options.MinStreams = 8
}
protocol, err := ParseProtocol(options.Protocol)
if err != nil {
return nil, err
}
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
}
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
stream, err := c.openStream()
if err != nil {
return nil, err
var brutalOptions mux.BrutalOptions
if options.Brutal != nil && options.Brutal.Enabled {
brutalOptions = mux.BrutalOptions{
Enabled: true,
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
}
return &ClientConn{Conn: stream, destination: destination}, nil
case N.NetworkUDP:
stream, err := c.openStream()
if err != nil {
return nil, err
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid upload speed")
}
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
stream, err := c.openStream()
if err != nil {
return nil, err
}
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
}
func (c *Client) openStream() (net.Conn, error) {
var (
session abstractSession
stream net.Conn
err error
)
for attempts := 0; attempts < 2; attempts++ {
session, err = c.offer()
if err != nil {
continue
}
stream, err = session.Open()
if err != nil {
continue
}
break
}
if err != nil {
return nil, err
}
return &wrapStream{stream}, nil
}
func (c *Client) offer() (abstractSession, error) {
c.access.Lock()
defer c.access.Unlock()
sessions := make([]abstractSession, 0, c.maxConnections)
for element := c.connections.Front(); element != nil; {
if element.Value.IsClosed() {
nextElement := element.Next()
c.connections.Remove(element)
element = nextElement
continue
}
sessions = append(sessions, element.Value)
element = element.Next()
}
sLen := len(sessions)
if sLen == 0 {
return c.offerNew()
}
session := common.MinBy(sessions, abstractSession.NumStreams)
numStreams := session.NumStreams()
if numStreams == 0 {
return session, nil
}
if c.maxConnections > 0 {
if sLen >= c.maxConnections || numStreams < c.minStreams {
return session, nil
}
} else {
if c.maxStreams > 0 && numStreams < c.maxStreams {
return session, nil
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid download speed")
}
}
return c.offerNew()
}
func (c *Client) offerNew() (abstractSession, error) {
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
if err != nil {
return nil, err
}
if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
} else {
conn = &protocolConn{Conn: conn, protocol: c.protocol}
}
session, err := c.protocol.newClient(conn)
if err != nil {
return nil, err
}
c.connections.PushBack(session)
return session, nil
}
func (c *Client) Close() error {
c.access.Lock()
defer c.access.Unlock()
for _, session := range c.connections.Array() {
session.Close()
}
return nil
}
type ClientConn struct {
net.Conn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientConn) readResponse() error {
response, err := ReadStreamResponse(c.Conn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
return c.Conn.Read(b)
}
func (c *ClientConn) Write(b []byte) (n int, err error) {
if c.requestWrite {
return c.Conn.Write(b)
}
request := StreamRequest{
Network: N.NetworkTCP,
Destination: c.destination,
}
_buffer := buf.StackNewSize(requestLen(request) + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
buffer.Write(b)
_, err = c.Conn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(b), nil
}
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.requestWrite {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
if !c.responseRead {
return bufio.WriteTo0(c, w)
}
return bufio.Copy(w, c.Conn)
}
func (c *ClientConn) LocalAddr() net.Addr {
return c.Conn.LocalAddr()
}
func (c *ClientConn) RemoteAddr() net.Addr {
return c.destination.TCPAddr()
}
func (c *ClientConn) ReaderReplaceable() bool {
return c.responseRead
}
func (c *ClientConn) WriterReplaceable() bool {
return c.requestWrite
}
func (c *ClientConn) Upstream() any {
return c.Conn
}
type ClientPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(b) < int(length) {
return 0, io.ErrShortBuffer
}
return io.ReadFull(c.ExtendedConn, b[:length])
}
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
}
rLen := requestLen(request)
if len(payload) > 0 {
rLen += 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(b)
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
if err != nil {
return
}
return c.ExtendedConn.Write(b)
}
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes()))
}
bLen := buffer.Len()
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketConn) FrontHeadroom() int {
return 2
}
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return c.WriteBuffer(buffer)
}
func (c *ClientPacketConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr()
}
func (c *ClientPacketConn) Upstream() any {
return c.ExtendedConn
}
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
type ClientPacketAddrConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketAddrConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
addr = destination.UDPAddr()
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(p) < int(length) {
return 0, nil, io.ErrShortBuffer
}
n, err = io.ReadFull(c.ExtendedConn, p[:length])
return
}
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
PacketAddr: true,
}
rLen := requestLen(request)
if len(payload) > 0 {
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(p, M.SocksaddrFromNet(addr))
}
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
if err != nil {
return
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
if err != nil {
return
}
return c.ExtendedConn.Write(p)
}
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
return
}
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes(), destination))
}
bLen := buffer.Len()
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
common.Must(
M.SocksaddrSerializer.WriteAddrPort(header, destination),
binary.Write(header, binary.BigEndian, uint16(bLen)),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketAddrConn) FrontHeadroom() int {
return 2 + M.MaxSocksaddrLength
}
func (c *ClientPacketAddrConn) Upstream() any {
return c.ExtendedConn
return mux.NewClient(mux.Options{
Dialer: dialer,
Logger: logger,
Protocol: options.Protocol,
MaxConnections: options.MaxConnections,
MinStreams: options.MinStreams,
MaxStreams: options.MaxStreams,
Padding: options.Padding,
Brutal: brutalOptions,
})
}

View File

@@ -1,234 +0,0 @@
package mux
import (
"encoding/binary"
"io"
"net"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
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/rw"
"github.com/sagernet/smux"
"github.com/hashicorp/yamux"
)
var Destination = M.Socksaddr{
Fqdn: "sp.mux.sing-box.arpa",
Port: 444,
}
const (
ProtocolSMux Protocol = iota
ProtocolYAMux
)
type Protocol byte
func ParseProtocol(name string) (Protocol, error) {
switch name {
case "", "smux":
return ProtocolSMux, nil
case "yamux":
return ProtocolYAMux, nil
default:
return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
}
}
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Server(conn, nil)
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
return yamux.Server(conn, yaMuxConfig())
default:
panic("unknown protocol")
}
}
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Client(conn, nil)
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
return yamux.Client(conn, yaMuxConfig())
default:
panic("unknown protocol")
}
}
func yaMuxConfig() *yamux.Config {
config := yamux.DefaultConfig()
config.LogOutput = io.Discard
config.StreamCloseTimeout = C.TCPTimeout
config.StreamOpenTimeout = C.TCPTimeout
return config
}
func (p Protocol) String() string {
switch p {
case ProtocolSMux:
return "smux"
case ProtocolYAMux:
return "yamux"
default:
return "unknown"
}
}
const (
version0 = 0
)
type Request struct {
Protocol Protocol
}
func ReadRequest(reader io.Reader) (*Request, error) {
version, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
if version != version0 {
return nil, E.New("unsupported version: ", version)
}
protocol, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
if protocol > byte(ProtocolYAMux) {
return nil, E.New("unsupported protocol: ", protocol)
}
return &Request{Protocol: Protocol(protocol)}, nil
}
func EncodeRequest(buffer *buf.Buffer, request Request) {
buffer.WriteByte(version0)
buffer.WriteByte(byte(request.Protocol))
}
const (
flagUDP = 1
flagAddr = 2
statusSuccess = 0
statusError = 1
)
type StreamRequest struct {
Network string
Destination M.Socksaddr
PacketAddr bool
}
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
var flags uint16
err := binary.Read(reader, binary.BigEndian, &flags)
if err != nil {
return nil, err
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil {
return nil, err
}
var network string
var udpAddr bool
if flags&flagUDP == 0 {
network = N.NetworkTCP
} else {
network = N.NetworkUDP
udpAddr = flags&flagAddr != 0
}
return &StreamRequest{network, destination, udpAddr}, nil
}
func requestLen(request StreamRequest) int {
var rLen int
rLen += 1 // version
rLen += 2 // flags
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
return rLen
}
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
destination := request.Destination
var flags uint16
if request.Network == N.NetworkUDP {
flags |= flagUDP
}
if request.PacketAddr {
flags |= flagAddr
if !destination.IsValid() {
destination = Destination
}
}
common.Must(
binary.Write(buffer, binary.BigEndian, flags),
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
)
}
type StreamResponse struct {
Status uint8
Message string
}
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
var response StreamResponse
status, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
response.Status = status
if status == statusError {
response.Message, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return &response, nil
}
type wrapStream struct {
net.Conn
}
func (w *wrapStream) Read(p []byte) (n int, err error) {
n, err = w.Conn.Read(p)
err = wrapError(err)
return
}
func (w *wrapStream) Write(p []byte) (n int, err error) {
n, err = w.Conn.Write(p)
err = wrapError(err)
return
}
func (w *wrapStream) WriteIsThreadUnsafe() {
}
func (w *wrapStream) Upstream() any {
return w.Conn
}
func wrapError(err error) error {
switch err {
case yamux.ErrStreamClosed:
return io.EOF
default:
return err
}
}

65
common/mux/router.go Normal file
View File

@@ -0,0 +1,65 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type Router struct {
router adapter.ConnectionRouter
service *mux.Service
}
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
if !options.Enabled {
return router, nil
}
var brutalOptions mux.BrutalOptions
if options.Brutal != nil && options.Brutal.Enabled {
brutalOptions = mux.BrutalOptions{
Enabled: true,
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
}
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid upload speed")
}
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid download speed")
}
}
service, err := mux.NewService(mux.ServiceOptions{
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
return log.ContextWithNewID(ctx)
},
Logger: logger,
Handler: adapter.NewRouteContextHandler(router, logger),
Padding: options.Padding,
Brutal: brutalOptions,
})
if err != nil {
return nil, err
}
return &Router{router, service}, nil
}
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination == mux.Destination {
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
} else {
return r.router.RouteConnection(ctx, conn, metadata)
}
}
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -1,256 +0,0 @@
package mux
import (
"context"
"encoding/binary"
"io"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"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/rw"
)
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
request, err := ReadRequest(conn)
if err != nil {
return err
}
session, err := request.Protocol.newServer(conn)
if err != nil {
return err
}
var stream net.Conn
for {
stream, err = session.Accept()
if err != nil {
return err
}
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
}
}
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
stream = &wrapStream{stream}
request, err := ReadStreamRequest(stream)
if err != nil {
logger.ErrorContext(ctx, err)
return
}
metadata.Destination = request.Destination
if request.Network == N.NetworkTCP {
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
} else {
var packetConn N.PacketConn
if !request.PacketAddr {
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
} else {
logger.InfoContext(ctx, "inbound multiplex packet connection")
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
}
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
}
}
var _ N.HandshakeConn = (*ServerConn)(nil)
type ServerConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) Write(b []byte) (n int, err error) {
if c.responseWrite {
return c.ExtendedConn.Write(b)
}
_buffer := buf.StackNewSize(1 + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusSuccess),
common.Error(buffer.Write(b)),
)
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.responseWrite = true
return len(b), nil
}
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
if c.responseWrite {
return c.ExtendedConn.WriteBuffer(buffer)
}
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) FrontHeadroom() int {
if !c.responseWrite {
return 1
}
return 0
}
func (c *ServerConn) Upstream() any {
return c.ExtendedConn
}
var (
_ N.HandshakeConn = (*ServerPacketConn)(nil)
_ N.PacketConn = (*ServerPacketConn)(nil)
)
type ServerPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
responseWrite bool
}
func (c *ServerPacketConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
destination = c.destination
return
}
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketConn) FrontHeadroom() int {
if !c.responseWrite {
return 3
}
return 2
}
var (
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
)
type ServerPacketAddrConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if buffer.FreeLen() < int(length) {
return destination, io.ErrShortBuffer
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
return
}
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketAddrConn) FrontHeadroom() int {
if !c.responseWrite {
return 3 + M.MaxSocksaddrLength
}
return 2 + M.MaxSocksaddrLength
}

View File

@@ -1,91 +0,0 @@
package mux
import (
"io"
"net"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/smux"
)
type abstractSession interface {
Open() (net.Conn, error)
Accept() (net.Conn, error)
NumStreams() int
Close() error
IsClosed() bool
}
var _ abstractSession = (*smuxSession)(nil)
type smuxSession struct {
*smux.Session
}
func (s *smuxSession) Open() (net.Conn, error) {
return s.OpenStream()
}
func (s *smuxSession) Accept() (net.Conn, error) {
return s.AcceptStream()
}
type protocolConn struct {
net.Conn
protocol Protocol
protocolWritten bool
}
func (c *protocolConn) Write(p []byte) (n int, err error) {
if c.protocolWritten {
return c.Conn.Write(p)
}
_buffer := buf.StackNewSize(2 + len(p))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeRequest(buffer, Request{
Protocol: c.protocol,
})
common.Must(common.Error(buffer.Write(p)))
n, err = c.Conn.Write(buffer.Bytes())
if err == nil {
n--
}
c.protocolWritten = true
return n, err
}
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.protocolWritten {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *protocolConn) Upstream() any {
return c.Conn
}
type vectorisedProtocolConn struct {
protocolConn
N.VectorisedWriter
}
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
if c.protocolWritten {
return c.VectorisedWriter.WriteVectorised(buffers)
}
c.protocolWritten = true
_buffer := buf.StackNewSize(2)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeRequest(buffer, Request{
Protocol: c.protocol,
})
return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
}

View File

@@ -0,0 +1,32 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type V2RayLegacyRouter struct {
router adapter.ConnectionRouter
logger logger.ContextLogger
}
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
return &V2RayLegacyRouter{router, logger}
}
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
}
return r.router.RouteConnection(ctx, conn, metadata)
}
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ import (
"unicode"
"unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -82,9 +81,7 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n
return 0, 0, E.Cause(err, "write netlink request")
}
_buffer := buf.StackNew()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
buffer := buf.New()
defer buffer.Release()
n, err := syscall.Read(socket, buffer.FreeBytes())

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

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

View File

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

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