Compare commits

...

1642 Commits

Author SHA1 Message Date
世界
c3cc010880
documentation: Bump version 2025-06-06 23:17:46 +08:00
世界
1920c191be
Fix systemd package 2025-06-06 23:17:46 +08:00
世界
e0ac459204
Fix missing home for derp service 2025-06-06 23:17:46 +08:00
Zero Clover
09fb897805
documentation: Fix services 2025-06-06 23:17:46 +08:00
世界
a1b3d891a3
Fix dns.client_subnet ignored 2025-06-06 23:17:46 +08:00
世界
d866a40469
documentation: Minor fixes 2025-06-06 23:17:46 +08:00
世界
45cd04b07e
Fix tailscale forward 2025-06-06 23:17:46 +08:00
世界
2cf0528c4d
Minor fixes 2025-06-06 23:17:46 +08:00
世界
905a2ded93
Add SSM API service 2025-06-06 23:17:46 +08:00
世界
cb3c0829c5
Add resolved service and DNS server 2025-06-06 23:17:45 +08:00
世界
1a8f6e053d
Add DERP service 2025-06-06 23:17:27 +08:00
世界
99a09a6ce5
Add service component type 2025-06-06 23:17:27 +08:00
世界
01b4c7fcdd
Fix tproxy tcp control 2025-06-06 23:17:27 +08:00
愚者
fe89f946c1
release: Fix build tags for android
Signed-off-by: 愚者 <11926619+FansChou@users.noreply.github.com>
2025-06-06 23:17:27 +08:00
世界
6c17c7a8f5
prevent creation of bind and mark controls on unsupported platforms 2025-06-06 23:17:27 +08:00
PuerNya
ea067e5478
documentation: Fix description of reject DNS action behavior 2025-06-06 23:17:27 +08:00
Restia-Ashbell
75af9a824e
Fix TLS record fragment 2025-06-06 23:17:27 +08:00
世界
a5d4a42119
Add missing accept_routes option for Tailscale 2025-06-06 23:17:26 +08:00
世界
9821fbc3e3
Add TLS record fragment support 2025-06-06 23:17:26 +08:00
世界
c0408ad1de
release: Update Go to 1.24.3 2025-06-06 23:17:26 +08:00
世界
6b0e861afa
Fix set edns0 client subnet 2025-06-06 23:17:26 +08:00
世界
e32d686d6c
Update minor dependencies 2025-06-06 23:17:26 +08:00
世界
844308e128
Update certmagic and providers 2025-06-06 23:17:26 +08:00
世界
93c14db281
Update protobuf and grpc 2025-06-06 23:17:26 +08:00
世界
b893a27dfc
Add control options for listeners 2025-06-06 23:17:25 +08:00
世界
d39960fa23
Update quic-go to v0.52.0 2025-06-06 23:17:25 +08:00
世界
ba0badd4bf
Update utls to v1.7.2 2025-06-06 23:17:25 +08:00
世界
cfbb5d63d5
Handle EDNS version downgrade 2025-06-06 23:16:21 +08:00
世界
8447a3edfe
documentation: Fix anytls padding scheme description 2025-06-06 23:16:21 +08:00
安容
1a9747a531
Report invalid DNS address early 2025-06-06 23:16:20 +08:00
世界
583ecbea3b
Fix wireguard listen_port 2025-06-06 23:16:20 +08:00
世界
bb6c8535a5
clash-api: Add more meta api 2025-06-06 23:16:19 +08:00
世界
10d90e4acc
Fix DNS lookup 2025-06-06 23:16:19 +08:00
世界
e625012219
Fix fetch ECH configs 2025-06-06 23:16:19 +08:00
reletor
670863fd5b
documentation: Minor fixes 2025-06-06 23:16:19 +08:00
caelansar
f7cf87142f
Fix callback deletion in UDP transport 2025-06-06 23:16:18 +08:00
世界
2597a68a01
documentation: Try to make the play review happy 2025-06-06 23:16:18 +08:00
世界
7354332daa
Fix missing handling of legacy domain_strategy options 2025-06-06 23:16:18 +08:00
世界
a0d382fc4e
Improve local DNS server 2025-06-06 23:16:18 +08:00
anytls
a6da8b6654
Update anytls
Co-authored-by: anytls <anytls>
2025-06-06 23:16:17 +08:00
世界
7385616cca
Fix DNS dialer 2025-06-06 23:16:17 +08:00
世界
4b6784b446
release: Skip override version for iOS 2025-06-06 23:16:16 +08:00
iikira
68579bb93b
Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-06-06 23:16:16 +08:00
ReleTor
6aace7b1b7
Fix fetch ECH configs 2025-06-06 23:16:16 +08:00
世界
148234b742
Allow direct outbounds without domain_resolver 2025-06-06 23:16:16 +08:00
世界
97b7a451be
Fix Tailscale dialer 2025-06-06 23:16:15 +08:00
dyhkwong
73b67e0b48
Fix DNS over QUIC stream close 2025-06-06 23:16:15 +08:00
anytls
88b4d04d59
Update anytls
Co-authored-by: anytls <anytls>
2025-06-06 23:16:15 +08:00
Rambling2076
d1ec6c6dd2
Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-06-06 23:16:14 +08:00
世界
523825336a
Fail when default DNS server not found 2025-06-06 23:16:14 +08:00
世界
032565a026
Update gVisor to 20250319.0 2025-06-06 23:16:14 +08:00
世界
aeea24ae30
Explicitly reject detour to empty direct outbounds 2025-06-06 23:16:14 +08:00
世界
af22549f1a
Add netns support 2025-06-06 23:16:14 +08:00
世界
57b17ceb4b
Add wildcard name support for predefined records 2025-06-06 23:16:13 +08:00
世界
3dd308e7c3
Remove map usage in options 2025-06-06 23:16:13 +08:00
世界
7f75195d86
Fix unhandled DNS loop 2025-06-06 23:16:13 +08:00
世界
2fe4cad905
Add wildcard-sni support for shadow-tls inbound 2025-06-06 23:16:12 +08:00
k9982874
f55eb75a53
Add ntp protocol sniffing 2025-06-06 23:16:12 +08:00
世界
5ffb5b6ad2
option: Fix marshal legacy DNS options 2025-06-06 23:16:12 +08:00
世界
a1d5931759
Make domain_resolver optional when only one DNS server is configured 2025-06-06 23:16:12 +08:00
世界
9e68e909cb
Fix DNS lookup context pollution 2025-06-06 23:16:11 +08:00
世界
117e8b76cc
Fix http3 DNS server connecting to wrong address 2025-06-06 23:16:11 +08:00
Restia-Ashbell
d2f83bfd50
documentation: Fix typo 2025-06-06 23:16:11 +08:00
anytls
eaef13febe
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-06-06 23:16:11 +08:00
k9982874
0110c69dc9
Fix hosts DNS server 2025-06-06 23:16:10 +08:00
世界
fb2f5af1fb
Fix UDP DNS server crash 2025-06-06 23:16:10 +08:00
世界
1553923118
documentation: Fix missing ip_accept_any DNS rule option 2025-06-06 23:16:10 +08:00
世界
0ada49489d
Fix anytls dialer usage 2025-06-06 23:16:10 +08:00
世界
95d5ca9393
Move predefined DNS server to rule action 2025-06-06 23:16:10 +08:00
世界
6cebbb4590
Fix domain resolver on direct outbound 2025-06-06 23:16:09 +08:00
Zephyruso
0ef81bb5ef
Fix missing AnyTLS display name 2025-06-06 23:16:09 +08:00
anytls
0d30a1df9d
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-06-06 23:16:09 +08:00
Estel
563499d2f9
documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-06-06 23:16:08 +08:00
TargetLocked
f10c0c1c8d
Fix parsing legacy DNS options 2025-06-06 23:16:08 +08:00
世界
428074d88b
Fix DNS fallback 2025-06-06 23:16:07 +08:00
世界
fa18832ad2
documentation: Fix missing hosts DNS server 2025-06-06 23:16:07 +08:00
anytls
87bce2de29
Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-06-06 23:16:06 +08:00
ReleTor
f5020554e4
documentation: Minor fixes 2025-06-06 23:16:06 +08:00
libtry486
31f3623b8a
documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-06-06 23:16:05 +08:00
Alireza Ahmadi
bb42657177
Fix Outbound deadlock 2025-06-06 23:16:05 +08:00
世界
f19ff7eca7
documentation: Fix AnyTLS doc 2025-06-06 23:16:05 +08:00
anytls
8e45133f2e
Add AnyTLS protocol 2025-06-06 23:16:04 +08:00
世界
63df88675f
Migrate to stdlib ECH support 2025-06-06 23:16:04 +08:00
世界
0423244298
Add fallback local DNS server for iOS 2025-06-06 23:16:03 +08:00
世界
a5f1af9587
Get darwin local DNS server from libresolv 2025-06-06 23:16:03 +08:00
世界
112817c1a4
Improve resolve action 2025-06-06 23:16:02 +08:00
世界
6e91de51f1
Add back port hopping to hysteria 1 2025-06-06 23:16:02 +08:00
xchacha20-poly1305
efc5c542fb
Remove single quotes of raw Moziila certs 2025-06-06 23:16:02 +08:00
世界
f1b569c7d1
Add Tailscale endpoint 2025-06-06 23:16:02 +08:00
世界
a752197d5e
Build legacy binaries with latest Go 2025-06-06 23:16:01 +08:00
世界
65517d4513
documentation: Remove outdated icons 2025-06-06 23:16:01 +08:00
世界
ccf4fa4d3a
documentation: Certificate store 2025-06-06 23:16:01 +08:00
世界
18dbb823a1
documentation: TLS fragment 2025-06-06 23:16:01 +08:00
世界
4ec058e91a
documentation: Outbound domain resolver 2025-06-06 23:16:01 +08:00
世界
6eed06b2c2
documentation: Refactor DNS 2025-06-06 23:16:00 +08:00
世界
dd209cc9d5
Add certificate store 2025-06-06 23:16:00 +08:00
世界
b0c0a6b07d
Add TLS fragment support 2025-06-06 23:15:59 +08:00
世界
951a8fabbf
refactor: Outbound domain resolver 2025-06-06 23:15:59 +08:00
世界
928298b528
refactor: DNS 2025-06-06 23:15:59 +08:00
世界
5b84fa0137
Fix default network strategy 2025-06-06 14:50:38 +08:00
世界
2bb85ac8a1
Fix slowOpenConn 2025-06-06 14:39:40 +08:00
世界
43a9016c83
Fix leak in hijack-dns 2025-06-06 14:28:09 +08:00
世界
255068fd40
Bump version 2025-06-04 23:32:10 +08:00
世界
098a00b025
Fix v2ray websocket transport 2025-06-04 23:23:36 +08:00
世界
dba0b5276b
Bump version 2025-06-04 20:06:38 +08:00
Sentsuki
78ae935468
documentation: Fix typo
Signed-off-by: Sentsuki <52487960+Sentsuki@users.noreply.github.com>
2025-06-04 20:06:38 +08:00
Mahdi
3ea5f76470
Fix nil logger at v2rayhttp server 2025-06-04 20:06:20 +08:00
世界
b4d294c05e
Fix TUIC read buffer 2025-06-04 20:03:51 +08:00
世界
83cf5f5c6a
Fix ws closed error message 2025-05-27 14:30:07 +08:00
世界
e7b3a8eebe
Fix vmess read request 2025-05-27 14:11:05 +08:00
世界
ee3a42a67e
Fix none method read buffer 2025-05-27 14:03:48 +08:00
世界
50227c0f5f
Fix sniff action 2025-05-26 18:24:35 +08:00
世界
bc5eb1e1a5
Fix RoutePacketConnectionEx 2025-05-24 08:14:43 +08:00
世界
995267a042
Remove wrong ALPNs in DOH/DOH3 2025-05-24 08:00:13 +08:00
世界
41226a6075
Fix interface finder 2025-05-23 10:57:38 +08:00
世界
81d32181ce
Fix update route address set 2025-05-20 19:46:54 +08:00
世界
c5ecca3938
Bump version 2025-05-18 16:48:44 +08:00
世界
900888731c
Fix DNS reject response 2025-05-13 18:05:31 +08:00
世界
13e648e4b1
Fix set edns0 subnet 2025-05-07 15:12:17 +08:00
世界
aff12ff671
Bump version 2025-05-05 09:37:47 +08:00
世界
101fb88255
Fix allocator put 2025-05-05 09:37:44 +08:00
世界
8b489354e4
Undeprecate the block outbound 2025-05-04 18:45:53 +08:00
世界
7dea6eb7a6
Fix missing read waiter for cancelers 2025-05-04 18:14:21 +08:00
世界
af1bfe4e3e
Make rule_set.format optional 2025-05-04 18:14:21 +08:00
世界
d574e9eb52
Update smux to v1.5.34 2025-04-30 19:39:15 +08:00
世界
2d7df1e1f2
Fix hysteria bytes format 2025-04-29 20:45:19 +08:00
世界
1c0ffcf5b1
Fix counter position in auto redirect dnat rules 2025-04-28 11:20:23 +08:00
世界
348cc39975
Bump version 2025-04-27 21:33:31 +08:00
世界
987899f94a
Fix usages of wireguard listener 2025-04-27 21:29:23 +08:00
世界
d8b2d5142f
Fix panic on some stupid input 2025-04-25 16:03:58 +08:00
世界
134802d1ee
Fix ssh outbound 2025-04-25 16:03:57 +08:00
世界
e5e81b4de1
Fix wireguard listening 2025-04-25 16:03:57 +08:00
世界
300c961efa
option: Fix listable again and again 2025-04-25 16:03:57 +08:00
世界
7c7f512405
option: Fix omitempty reject method 2025-04-25 16:03:57 +08:00
世界
03e8d029c2
release: Fix apt-get install 2025-04-25 16:03:57 +08:00
世界
787b5f1931
Fix set wireguard reserved on Linux 2025-04-25 16:03:57 +08:00
世界
56a7624618
Fix vmess working with zero uuids 2025-04-25 16:03:57 +08:00
世界
3a84acf122
Fix hysteria1 server panic 2025-04-25 16:03:57 +08:00
世界
f600e02e47
Fix DNS crash 2025-04-25 16:03:57 +08:00
世界
e6d19de58a
Fix overriding address 2025-04-22 14:55:44 +08:00
dyhkwong
f2bbf6b2aa
Fix sniffer errors override each others
* Fix sniffer errors override each others

* Do not return ErrNeedMoreData if header is not expected
2025-04-22 14:44:55 +08:00
dyhkwong
c54d50fd36
Fix websocket detour
Signed-off-by: trimgop <20010323+trimgop@users.noreply.github.com>
Co-authored-by: trimgop <20010323+trimgop@users.noreply.github.com>
2025-04-22 14:44:34 +08:00
世界
6a051054db
release: Fix packages 2025-04-19 19:12:01 +08:00
世界
49498f6439
Bump version 2025-04-18 08:54:40 +08:00
世界
144a890c71
release: Add openwrt packages 2025-04-18 08:54:40 +08:00
世界
afb4993445
Fix urltest outbound 2025-04-18 08:54:40 +08:00
世界
4c9455b944
Fix wireguard endpoint 2025-04-18 08:54:40 +08:00
世界
5fdc051a08
Fix override_port in direct inbound 2025-04-16 17:04:13 +08:00
世界
cb68a40c43
documentation: Update actual behaviors of auto_redirect and strict_route 2025-04-12 13:06:16 +08:00
纳西妲 · Nahida
023218e6e7
Fix build will fail when use space to split each tag 2025-04-12 13:06:16 +08:00
世界
2a24b94b8d
Minor fixes 2025-04-12 13:06:15 +08:00
世界
c6531cf184
Fix NTP service 2025-04-12 13:06:15 +08:00
世界
d4fa0ed349
Improve auto redirect 2025-04-12 13:06:10 +08:00
世界
10874d2dc4
Bump version 2025-04-08 14:34:09 +08:00
Fei1Yang
5adaf1ac75
Mark config file as noreplace for rpm 2025-04-08 14:21:08 +08:00
世界
9668ea69b8
Fix windows process searcher 2025-04-08 14:16:27 +08:00
testing
ae9bc7acf1
documentation: Fix typo
Signed-off-by: testing <58134720+testing765@users.noreply.github.com>
2025-04-08 14:16:23 +08:00
世界
594ee480a2
option: Fix listable 2025-04-08 14:16:23 +08:00
世界
a15b5a2463
Fix no_drop not work 2025-04-08 14:16:23 +08:00
Mahdi
991e755789
Fix conn copy 2025-04-08 14:16:22 +08:00
世界
97d41ffde8
Improve pause management 2025-04-08 14:16:22 +08:00
世界
24af0766ac
Fix uTP sniffer 2025-04-08 14:16:22 +08:00
世界
af17eaa537
Improve sniffer 2025-04-08 14:16:22 +08:00
世界
3adc10a797
Fix hysteria2 close 2025-04-08 14:16:22 +08:00
xchacha20-poly1305
5eeef6b28e
Fix multiple trackers 2025-04-08 14:16:22 +08:00
世界
f4c29840c3
Fix DNS sniffer 2025-03-31 20:45:04 +08:00
世界
47fc3ebda4
Add duplicate tag check 2025-03-29 23:10:22 +08:00
世界
9774a659b0
Fix DoQ / truncate DNS message 2025-03-29 17:41:22 +08:00
世界
2e4a6de4e7
release: Fix read tag 2025-03-27 20:30:57 +08:00
世界
a530e424e9
Bump version 2025-03-27 18:17:39 +08:00
世界
0bfd487ee9
Fix udpnat2 handler again 2025-03-27 18:17:39 +08:00
世界
6aae834493
release: Fix workflow 2025-03-27 18:17:39 +08:00
世界
f56131f38e
Make linter happy 2025-03-24 20:38:42 +08:00
世界
273a11d550
Fix crash on udpnat2 handler 2025-03-24 18:14:32 +08:00
世界
ae8ce75e41
Fix websocket crash 2025-03-24 17:44:14 +08:00
世界
d6d94b689f
release: Replace goreleaser build with scripts 2025-03-24 13:48:37 +08:00
世界
30d785f1ee
release: Use fake goreleaser key 2025-03-21 22:25:51 +08:00
世界
db5ec3cdfc
Fix connectionCopyEarly 2025-03-21 10:51:16 +08:00
世界
9aca54d039
Fix socks5 UDP 2025-03-16 14:46:44 +08:00
世界
d55d5009c2
Fix processing multiple sniffs 2025-03-16 09:21:54 +08:00
世界
4f3ee61104
Fix copy early conn 2025-03-15 08:09:04 +08:00
世界
96eb98c00a
Fix httpupgrade crash 2025-03-14 17:17:28 +08:00
世界
68ce9577c6
Fix context in v2ray http transports 2025-03-14 17:07:17 +08:00
世界
3ae036e997
Downgrade goreleaser to stable since nfpm fixed 2025-03-13 18:53:19 +08:00
世界
5da2d1d470
release: Fix goreleaser version 2025-03-12 16:15:50 +08:00
世界
8e2baf40f1
Bump version 2025-03-11 20:18:34 +08:00
世界
c24c40dfee
platform: Fix android start 2025-03-11 20:18:34 +08:00
世界
32e52ce1ed
Fix udp nat for fakeip 2025-03-11 19:09:27 +08:00
世界
ed46438359
release: Use nightly goreleaser to fix rpm bug 2025-03-11 13:29:08 +08:00
世界
0b5490d5a3
Fix resolve domain for WireGuard 2025-03-11 12:02:25 +08:00
Tal Rasha
2d73ef511d
Fix grpclite memory leak
Co-authored-by: talrasha007 <talrasha007@gmail.om>
2025-03-10 14:48:02 +08:00
Mahdi
63e6c85f6f
Fix shadowsocks UoT 2025-03-10 14:47:59 +08:00
世界
8946a6d2d0
release: Use latest goreleaser 2025-03-09 15:27:04 +08:00
世界
d3132645fb
documentation: Fix description of the UoT protocol 2025-03-09 15:26:42 +08:00
世界
373f158fe0
Fix download external ui with query params 2025-03-09 15:26:36 +08:00
世界
ce36835fab
Fix override destination 2025-03-09 15:25:06 +08:00
世界
619fa671d7
Skip binding to the default interface as it will fail on some Android devices 2025-02-26 07:25:35 +08:00
世界
eb07c7a79e
Bump version 2025-02-24 07:27:55 +08:00
Gavin Luo
7eb3535094
release: Fix systemd permissions 2025-02-24 07:27:55 +08:00
世界
93b68312cf
platform: Add update WIFI state func 2025-02-23 08:35:30 +08:00
世界
97ce666e43
Fix http.FileServer short write 2025-02-23 08:35:30 +08:00
世界
4000e1e66d
release: Fix update android version 2025-02-23 08:35:30 +08:00
世界
270740e859
Fix crash on route address set update 2025-02-23 08:35:30 +08:00
世界
6cad142cfe
Bump Go to go1.24 2025-02-23 08:35:30 +08:00
世界
093013687c
Fix sniff QUIC hidden in three or more packets 2025-02-18 18:14:59 +08:00
世界
ff31c469a0
Override version 2025-02-11 15:55:15 +08:00
世界
fbe390268c
Bump version 2025-02-11 01:32:14 +08:00
世界
07ac01dcb7
platform: Update NDK to r28 2025-02-11 01:32:14 +08:00
ReleTor
badfdb62cd
documentation: Fixes 2025-02-11 01:32:14 +08:00
printfer
986a410b30
documentation: Fix migration links 2025-02-11 01:32:14 +08:00
世界
9db2d58545
Fix override address 2025-02-11 01:32:14 +08:00
世界
4eed46ac59
Fix respond ICMP echo 2025-02-10 15:12:10 +08:00
世界
abc38d1dab
Fix udpnat2 crash 2025-02-10 15:11:26 +08:00
世界
8d6c4f1289
release: Skip testflight when another build in review 2025-02-06 12:02:47 +08:00
世界
a2d40eb8b8
Fix override UDP destination 2025-02-06 11:20:35 +08:00
世界
17b502bb4b
Update dependencies 2025-02-06 09:08:52 +08:00
世界
a0d4421085
Update quic-go to v0.49.0 2025-02-06 08:50:21 +08:00
世界
0d443072d1
Fix panic in auto-redirect initialize 2025-02-06 08:49:25 +08:00
世界
c9fb99b799
Fix missing ENOTCONN in IsClosed check 2025-02-06 08:48:49 +08:00
世界
92d245ad04
Bump version 2025-02-05 09:59:52 +08:00
世界
0908627297
Fix crash on remote rule-set stop 2025-02-05 08:58:10 +08:00
世界
7f79458b4f
Minor updates 2025-02-01 19:49:33 +08:00
世界
9b4c11ba95
Fix rule-set not closed 2025-02-01 19:49:33 +08:00
世界
27c31eac5d
Fix local rule-set not updated 2025-02-01 19:42:21 +08:00
世界
bab8dc0b82
Fix missing handshake for early conn 2025-01-31 12:57:35 +08:00
世界
d09d2fb665
Bump version 2025-01-30 15:09:54 +08:00
世界
e64cf3b7df
Do not set address sets to routes on Apple platforms
Network Extension was observed to stop for unknown reasons
2025-01-27 13:40:39 +08:00
世界
9b73222314
documentation: Bump version 2025-01-27 10:53:14 +08:00
世界
3923b57abf
release: Update NDK to r28-beta3 2025-01-27 10:53:14 +08:00
世界
4807e64609
documentation: Fix typo 2025-01-27 10:04:07 +08:00
世界
eeb37d89f1
Fix rule-set upgrade command 2025-01-27 09:50:27 +08:00
世界
08c1ec4b7e
Fix legacy routes 2025-01-27 09:50:27 +08:00
HystericalDragon
6b4cf67add
Fix endpoints not close 2025-01-27 09:50:27 +08:00
世界
e65926fd08
Fix tests 2025-01-13 15:14:30 +08:00
世界
f2ec319fe1
Fix system time 2025-01-13 15:14:30 +08:00
世界
32377a61b7
Add port hopping for hysteria2 2025-01-13 15:14:30 +08:00
世界
7aac801ccd
tun: Set address sets to routes 2025-01-13 15:14:30 +08:00
世界
96fdf59ee4
Fix default dialer on legacy xiaomi systems 2025-01-13 15:14:30 +08:00
世界
50b8f3ab94
Add rule-set merge command 2025-01-13 15:14:30 +08:00
世界
ff7aaf977b
Fix DNS match 2025-01-13 15:14:30 +08:00
世界
9a1efbe54d
Fix domain strategy 2025-01-13 15:14:30 +08:00
世界
906c21f458
Fix time service 2025-01-13 15:14:30 +08:00
世界
d5e7af7a7e
Fix socks5 UDP implementation 2025-01-13 15:14:30 +08:00
世界
4d41f03bd5
clash-api: Fix missing endpoints 2025-01-13 15:14:30 +08:00
世界
30704a15a7
hysteria2: Add more masquerade options 2025-01-13 15:14:30 +08:00
世界
83889178ed
Improve timeouts 2025-01-13 15:14:30 +08:00
世界
1d2720bf5e
Add UDP timeout route option 2025-01-13 15:14:30 +08:00
世界
c4b6d0eadb
Make GSO adaptive 2025-01-13 15:14:30 +08:00
世界
0c66888691
Fix lint 2025-01-13 15:14:30 +08:00
世界
68781387fe
refactor: WireGuard endpoint 2025-01-13 15:14:30 +08:00
世界
fd299a0961
refactor: connection manager 2025-01-13 15:14:30 +08:00
世界
285a82050c
documentation: Fix typo 2025-01-13 15:14:30 +08:00
世界
2dbb8c55c9
Add override destination to route options 2025-01-13 15:14:30 +08:00
世界
effcf39469
Add dns.cache_capacity 2025-01-13 15:14:30 +08:00
世界
9db9484863
Refactor multi networks strategy 2025-01-13 15:14:30 +08:00
世界
ca813f461b
documentation: Remove unused titles 2025-01-13 15:14:30 +08:00
世界
bb46cdb2b3
Add multi network dialing 2025-01-13 15:14:30 +08:00
世界
dcb10c21a1
documentation: Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
05ea0ca00e
Add network_[type/is_expensive/is_constrained] rule items 2025-01-13 15:14:29 +08:00
世界
c098f282b1
Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
ecf82d197c
refactor: Platform Interfaces 2025-01-13 15:14:29 +08:00
世界
9afe75586a
refactor: Extract services form router 2025-01-13 15:14:29 +08:00
世界
a1be455202
refactor: Modular network manager 2025-01-13 15:14:29 +08:00
世界
19fb214226
refactor: Modular inbound/outbound manager 2025-01-13 15:14:29 +08:00
世界
28ec898a8c
documentation: Add rule action 2025-01-13 15:14:29 +08:00
世界
467b1bbeeb
documentation: Update the scheduled removal time of deprecated features 2025-01-13 15:14:29 +08:00
世界
02ab8ce806
documentation: Remove outdated icons 2025-01-13 15:14:29 +08:00
世界
ce69e620e9
Migrate bad options to library 2025-01-13 15:14:29 +08:00
世界
1133cf3ef5
Implement udp connect 2025-01-13 15:14:29 +08:00
世界
59a607e303
Implement new deprecated warnings 2025-01-13 15:14:29 +08:00
世界
313be3d7a4
Improve rule actions 2025-01-13 15:14:29 +08:00
世界
4fe40fcee0
Remove unused reject methods 2025-01-13 15:14:29 +08:00
世界
e233fd4fe5
refactor: Modular inbounds/outbounds 2025-01-13 15:14:29 +08:00
世界
9f7683818f
Implement dns-hijack 2025-01-13 15:14:29 +08:00
世界
179e3cb2f5
Implement resolve(server) 2025-01-13 15:14:29 +08:00
世界
41b960552d
Implement TCP and ICMP rejects 2025-01-13 15:14:29 +08:00
世界
8304295c48
Crazy sekai overturns the small pond 2025-01-13 15:14:29 +08:00
世界
253b41936e
Bump version 2025-01-11 20:58:00 +08:00
世界
ce5b4b06b5
auto-redirect: Fix fetch interfaces 2025-01-07 21:24:52 +08:00
世界
50f5006c43
Fix leak in reality server 2025-01-07 17:27:10 +08:00
世界
e42ff22c2e
quic: Fix source not unwrapped 2025-01-07 17:27:10 +08:00
世界
578571b972
Bump version 2025-01-02 15:21:13 +08:00
世界
935beca45d
Fix check interface 2025-01-01 12:19:56 +08:00
世界
3e246f1173
Fix vmess mux leak 2025-01-01 12:18:56 +08:00
世界
1bc27a32c2
Fix linter configuration 2025-01-01 12:18:56 +08:00
世界
bc2e3960e4
Enable fix stack for Android 7 and 9 2024-12-28 15:06:57 +08:00
世界
9c4ab0bf33
Bump version 2024-12-21 17:20:56 +08:00
世界
27bdef34c7
release: Fix create app store version 2024-12-21 17:20:56 +08:00
世界
3c00099ed4
Fix process rule check 2024-12-21 17:15:41 +08:00
世界
2babf07f9a
Fix wireguard 2024-12-21 16:07:19 +08:00
世界
4795ed712b
release: Fix android version 2024-12-21 16:07:19 +08:00
世界
d4cd564dbe
release: Fix check tag 2024-12-21 16:07:19 +08:00
世界
1676e13d3e
Bump version 2024-12-17 13:43:17 +08:00
世界
50576084c6
release: Add publish testflight 2024-12-17 13:43:17 +08:00
世界
3a94e792a2
documentation: Fix uid range example 2024-12-15 13:56:25 +08:00
世界
9f69f41f68
Update http file server usage 2024-12-15 13:56:25 +08:00
世界
e6847ff50e
Add locale support ford deprecated messages 2024-12-15 02:57:08 +08:00
世界
2ac2589d14
release: Fix publish testflight 2024-12-15 02:57:08 +08:00
世界
64a94e8144
release: Fix UpdateBuildForAppStoreVersion 2024-12-14 20:18:36 +08:00
世界
3ed8a5c5d1
wireguard: Fix windows tun read 2024-12-14 20:18:36 +08:00
世界
0a922c6fe3
release: Fix Xcode version 2024-12-14 20:18:36 +08:00
世界
52f3a4226c
release: Add app store connect actions 2024-12-14 20:18:36 +08:00
世界
483d9fa503
release: Fix check prerelease 2024-12-14 20:18:36 +08:00
世界
dd9de694f8
release: Update macOS project version atomically 2024-12-14 20:18:36 +08:00
世界
5cdf5c1d9e
release: Fix play release 2024-12-14 20:18:36 +08:00
世界
cec7e47086
Add workaround for bulkBarrierPreWrite: unaligned arguments panic 2024-12-14 20:18:36 +08:00
世界
1e6a3f1f0b
release: Update debug iOS library build 2024-12-12 14:51:47 +08:00
世界
f0b6818b4c
Add workaround for golang/go#68760 2024-12-10 10:30:42 +08:00
世界
3032317918
release: Add workflow build 2024-12-10 09:25:11 +08:00
世界
db22f61846
Update NDK to r28-rc1 2024-12-09 15:11:38 +08:00
世界
8c3a98faa2
wireguard: Fix set reserved 2024-12-05 17:58:09 +08:00
世界
1e787cb607
Fix initial traffic value 2024-12-03 21:43:56 +08:00
世界
558585b01d
release: Set upload threads to 5 2024-12-03 17:36:19 +08:00
世界
6e7ecbd4f5
Fix wireguard listen 2024-11-30 12:20:48 +08:00
世界
5a661cde67
clashapi: Remove traffic loop 2024-11-28 12:57:56 +08:00
世界
3cc0e87cfb
Bump version 2024-11-27 10:45:24 +08:00
世界
effea5a2b3
Update quic-go to v0.48.2 2024-11-27 10:37:00 +08:00
世界
7f168c5ec6
release: Clean before iOS build 2024-11-27 10:35:20 +08:00
Zephyruso
0e9129ee3f
clashapi: Add mode list 2024-11-27 10:34:47 +08:00
世界
1086d5e665
Fix test tags 2024-11-23 12:20:12 +08:00
世界
d9102ba599
Fix build on bsd systems 2024-11-23 12:16:46 +08:00
世界
17019f1729
Bump version 2024-11-20 12:03:42 +08:00
世界
6be07ed51f
Fix start watcher 2024-11-20 11:32:53 +08:00
世界
af58e3bec0
Fix debug listener 2024-11-20 11:32:53 +08:00
世界
e58b549d0f
Fix "Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path" 2024-11-18 19:03:24 +08:00
zeetex
1d81996ceb
Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path 2024-11-18 14:09:54 +08:00
世界
97c47e72c4
Update dependencies 2024-11-18 14:07:00 +08:00
世界
122be275b0
release: Notarize macos standalone manually with --no-s3-acceleration 2024-11-18 14:07:00 +08:00
世界
0bb1132034
selector: Fix crash before start 2024-11-18 14:07:00 +08:00
世界
de14337b4b
Fix deprecated check 2024-11-18 14:07:00 +08:00
世界
1e07633914
Downgrade NDK to 26.2.11394342 2024-11-18 13:10:06 +08:00
世界
e3e203844e
Fix decompile rule-set 2024-11-18 13:10:06 +08:00
世界
84a102a6ef
Fix mux stream accept 2024-11-09 12:26:49 +08:00
世界
f1c76c4dde
Fix deprecated version check 2024-11-08 20:31:29 +08:00
世界
8df0aa5719
Downgrade NDK to r26d 2024-11-07 19:58:53 +08:00
世界
21faadb992
Uniq deprecated notes 2024-11-07 19:58:53 +08:00
世界
88099a304a
platform: Add SendNotification 2024-11-06 12:53:32 +08:00
世界
f504fb0d46
Revert "platform: Add openURL event"
This reverts commit 718cffea9a34c9f2d552728ee8b3f978aaf82c03.
2024-11-05 20:03:39 +08:00
世界
1d517b6ca5
platform: Add link flags 2024-11-05 18:28:13 +08:00
世界
b702d0b67a
Update dependencies 2024-11-05 18:28:03 +08:00
世界
a001e30d8b
platform: Remove SetTraceback("all") 2024-11-05 18:28:02 +08:00
世界
cdb93f0bb2
Fix "Fix metadata context" 2024-11-05 18:27:51 +08:00
世界
718cffea9a
platform: Add openURL event 2024-11-05 18:27:51 +08:00
世界
9585c53e9f
release: Add upload dSYMs 2024-10-30 15:35:20 +08:00
世界
d66d5cd457
Add deprecated warnings 2024-10-30 14:01:28 +08:00
世界
8c143feec8
Increase timeouts 2024-10-30 14:01:28 +08:00
世界
419058f466
Update NDK version 2024-10-30 14:01:13 +08:00
世界
1a6047a61b
Fix metadata context 2024-10-30 14:01:13 +08:00
世界
327bb35ddd
Rename HTTP start context 2024-10-30 14:01:13 +08:00
世界
6ed9a06394
Fix rule-set format 2024-10-25 22:12:47 +08:00
世界
b80ec55ba0
Bump version 2024-10-16 21:11:31 +08:00
世界
08718112ae
Retry system forwarder listen 2024-10-16 20:47:26 +08:00
TsingShui
956ee361df
Fix corrected improper use of reader and bReader
Co-authored-by: x_123 <x@a>
2024-10-16 20:45:18 +08:00
世界
e93d0408be
documentation: Fix release notes 2024-10-16 20:45:13 +08:00
世界
137832ff3e
Bump version 2024-10-13 21:17:59 +08:00
世界
3ede29fb6d
documentation: Improve theme 2024-10-13 13:07:18 +08:00
世界
82ab68b542
build: Fix find NDK 2024-10-13 13:07:18 +08:00
renovate[bot]
e55723d84d
[dependencies] Update actions/checkout digest to eef6144 2024-10-13 13:07:18 +08:00
世界
2f4d2d97f9
auto-redirect: Let fw4 take precedence over prerouting 2024-10-13 13:07:18 +08:00
世界
926d6f769e
Update utls to v1.6.7 2024-10-13 13:07:02 +08:00
srk24
846777cd0c
Add process_path_regex rule type 2024-10-13 13:07:02 +08:00
世界
06533b7a3b
clash-api: Add PNA support 2024-10-13 13:07:02 +08:00
世界
4a95558c53
Add RDP sniffer 2024-10-13 13:07:02 +08:00
世界
e39a28ed5a
Add SSH sniffer 2024-10-13 13:07:02 +08:00
世界
b2c708a3e6
Write close error to log 2024-10-13 13:07:02 +08:00
世界
a9209bb3e5
Add AdGuard DNS filter support 2024-10-13 13:07:02 +08:00
世界
9dc3bb975a
Improve QUIC sniffer 2024-10-13 13:07:02 +08:00
世界
3a7acaa92a
Add inline rule-set & Add reload for local rule-set 2024-10-13 13:07:02 +08:00
世界
6bebe2483b
Unique rule-set names 2024-10-13 13:07:02 +08:00
世界
93cf134995
Add accept empty DNS rule option 2024-10-13 13:07:02 +08:00
世界
ff7d8c9ba8
Add custom options for TUN auto-route and auto-redirect 2024-10-13 13:07:02 +08:00
世界
50f07b42f6
Improve base DNS transports & Minor fixes 2024-10-13 13:07:02 +08:00
世界
db3a0c636d
Add auto-redirect & Improve auto-route 2024-10-13 13:07:02 +08:00
世界
fec38f85cd
Add rule-set decompile command 2024-10-13 13:07:02 +08:00
世界
dcb0141646
Add IP address support for rule-set match match 2024-10-13 13:07:02 +08:00
世界
f4f5a3c925
Improve usages of json.Unmarshal 2024-10-13 13:07:02 +08:00
世界
9b8d6c1b73
Bump rule-set version 2024-10-13 13:07:02 +08:00
世界
2f776168de
Implement read deadline for QUIC based UDP inbounds 2024-10-13 13:07:02 +08:00
世界
923d3222b0
WTF is this 2024-10-13 13:07:01 +08:00
世界
bda93d516b
platform: Fix clash server reload on android 2024-10-13 13:06:57 +08:00
世界
7eec3fb57a
platform: Add log update interval 2024-10-13 13:06:57 +08:00
世界
b1d75812c5
platform: Prepare connections list 2024-10-13 13:06:55 +08:00
世界
d44e7d9834
Drop support for go1.18 and go1.19 2024-10-07 04:58:48 +08:00
世界
369bc7cea3
Add DTLS sniffer 2024-10-07 04:58:48 +08:00
iosmanthus
4b7a83da16
Introduce bittorrent related protocol sniffers
* Introduce bittorrent related protocol sniffers

including, sniffers of
1. BitTorrent Protocol (TCP)
2. uTorrent Transport Protocol (UDP)

Signed-off-by: iosmanthus <myosmanthustree@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2024-10-07 04:58:48 +08:00
世界
0f7154afbd
Update workflow to go1.23 2024-10-07 04:58:47 +08:00
世界
a06d10c3bc
Bump version 2024-10-07 04:34:48 +08:00
世界
63cc6cc76c
Fix Makefile 2024-10-07 04:34:48 +08:00
世界
d55c5b5cab
documentation: Update package status 2024-10-07 04:34:48 +08:00
世界
b624c2dcc7
Fix context used by DNS outbounds 2024-10-07 04:34:48 +08:00
世界
9415444ebd
Fix base path not applied to local rule-sets 2024-10-07 04:34:48 +08:00
世界
95606191d8
Add completions for linux packages 2024-10-07 04:34:48 +08:00
世界
e586d9e9bc
Bump version 2024-09-20 23:37:06 +08:00
世界
8c7eaa4477
Fix docker build 2024-09-20 23:37:06 +08:00
世界
8464c8cb7c
Fix version script 2024-09-20 21:10:15 +08:00
世界
39d7127651
Revert "Fix stream sniffer" 2024-09-20 20:40:02 +08:00
世界
e2077009c4
documentation: Update client status 2024-09-20 20:13:55 +08:00
世界
700a8eb425
Minor fixes 2024-09-20 20:13:14 +08:00
世界
3b0cba0852
Fix wireguard start 2024-09-20 20:12:52 +08:00
世界
f5554dd8b8
Bump version 2024-09-18 07:04:29 +08:00
世界
4d0362d530
Update macOS build workflow 2024-09-17 22:01:05 +08:00
世界
97ccd2ca04
documentation: Add sponsors page 2024-09-17 18:47:33 +08:00
世界
1ed6654ad4
Add mips64 build 2024-09-15 12:12:25 +08:00
世界
5385f75f53
documentation: Update build requirements 2024-09-15 12:10:00 +08:00
世界
ad97d4e11f
Fix disconnected interface selected as default in windows 2024-09-15 11:59:32 +08:00
世界
09d4e91b77
Fix cached conn eats up read deadlines 2024-09-15 11:56:04 +08:00
Monica
3dbdda9555
documentation: Fix dial.zh.md
The Chinese documentation incorrectly stated that the default value for the domain_strategy field in the direct outbound module is dns.strategy. The correct value should be inbound.domain_strategy, as specified in the English documentation. This commit corrects the Chinese documentation to align with the accurate behavior described in the English version.

Signed-off-by: Monica <1379531829@qq.com>
2024-09-15 11:53:03 +08:00
世界
1f4ed6ff8f
documentation: Update client status 2024-09-13 10:09:08 +08:00
世界
6ddbe19bc0
platform: Update bundle id 2024-09-12 17:55:53 +08:00
世界
d7205ecc60
Fix Makefile 2024-09-12 17:55:53 +08:00
世界
9e243e0ff9
gomobile: Fix go mod version 2024-09-10 23:05:13 +08:00
世界
02bc3e0a0a
Update quic-go to v0.47.0 2024-09-09 14:45:46 +08:00
世界
87be6dc235
Update README 2024-09-09 08:48:35 +08:00
世界
c1c30976dc
Improve docker workflow 2024-09-08 11:11:47 +08:00
世界
9bac18bcd1
wireguard: Fix events chan leak 2024-09-08 10:07:12 +08:00
世界
ceda5cc95d
clash-api: Fix bad redirect 2024-09-08 10:07:07 +08:00
世界
27d6b63e71
Fix stream sniffer 2024-09-08 10:07:07 +08:00
世界
b57abcc73c
tfo: Fix build with go1.23 2024-08-27 11:24:51 +08:00
世界
f1147965dd
documentation: Fix missing zh headline 2024-08-21 18:52:38 +08:00
世界
45f3234c73
documentation: Update package status 2024-08-21 11:39:07 +08:00
Mingye Wang
aae3fded32
documentation: Two updates
* Copyedit documentation

Close #1378

* remove yum, go full on dnf

fixes #2049
2024-08-21 11:32:43 +08:00
世界
090494faf5
Do not close bug and enhancement issues 2024-08-21 08:09:15 +08:00
世界
db5719e22f
Fix no error return when empty DNS cache retrieved 2024-08-20 23:23:48 +08:00
世界
064fb9b873
Fix direct dialer not resolving domain 2024-08-20 21:02:52 +08:00
世界
f6a1e123fc
Make linter happy 2024-08-19 18:42:02 +08:00
世界
3066dfe3b3
Bump version 2024-08-19 10:43:09 +08:00
Liu Bingyan
1128fdd8c7
documentation: Update injectable description 2024-08-19 07:40:31 +08:00
世界
cfd9879b17
documentation: Remove unused 2024-08-19 06:39:33 +08:00
世界
9ceb660c57
documentation: Announce that our apps on Apple platforms are no longer available 2024-08-19 06:39:33 +08:00
世界
7d00d7df28
Update quic-go to v0.46.0 2024-08-19 06:25:15 +08:00
世界
21b1ac26b9
Fix UDP conn stuck on sniff
This change only avoids permanent hangs. We need to implement read deadlines for UDP conns in 1.10 for server inbounds.
2024-08-18 11:27:12 +08:00
世界
7fec8d842e
Update golangci-lint configuration 2024-08-18 11:17:01 +08:00
世界
07c678fb85
Fix crash on Android when using process rules 2024-08-18 10:23:28 +08:00
世界
baecfc7778
Update quic-go to v0.45.2 2024-08-18 10:23:17 +08:00
世界
07de36ecdb
Fix tests 2024-08-03 12:46:38 +08:00
世界
2c8a8303cd
dns: Minor fixes 2024-07-27 11:04:57 +08:00
ruokeqx
e5991cae0b
Use unix.SysctlRaw for macOS 2024-07-22 12:42:22 +08:00
世界
1349acfd5a
Fix panic caused by possible generation of duplicate keys for domain_suffix 2024-07-17 16:02:17 +08:00
世界
98ff897f35
Fix reset outbounds 2024-07-13 17:46:22 +08:00
ayooh
6144c8e340
Fix default end value of the rangeItem 2024-07-13 17:45:32 +08:00
HystericalDragon
c8caac9f67
Fix v2rayquic default NextProtos (#1934) 2024-07-11 23:34:09 +08:00
printfer
81e9eda357
documentation: Fix typo 2024-07-07 16:09:41 +08:00
世界
7cba3da108
shadowsocks: Fix packet process for AEAD multi-user server 2024-07-05 17:09:09 +08:00
世界
82d06b43e7
vless: Fix missing deadline interfaces 2024-07-05 17:08:10 +08:00
世界
a7ac91f573
Move legacy vless implemetation to library 2024-07-03 20:17:32 +08:00
世界
0540a95a43
Fix v2ray-plugin 2024-07-02 14:08:17 +08:00
世界
94707dfcdd
Add name to errors from v2ray HTTP transports 2024-07-02 14:08:17 +08:00
世界
8a17043502
Fix command output for rule-set match 2024-06-26 12:26:35 +08:00
世界
b0aaa86806
Minor fixes 2024-06-25 13:14:45 +08:00
世界
8a2d3fbb28
Update quic-go to v0.45.1 & Filter HTTPS ipv4/6hint 2024-06-24 11:07:32 +08:00
renovate[bot]
4652019608
[dependencies] Update docker/build-push-action action to v6 2024-06-24 10:24:13 +08:00
世界
06fa5abf63
Fix non-IP queries accepted by address filter rules 2024-06-24 10:13:16 +08:00
世界
996fbbf0c3
docs: Switch to venv 2024-06-24 10:10:49 +08:00
renovate[bot]
142ff1b455
[dependencies] Update actions/checkout digest to 692973e 2024-06-17 13:05:54 +08:00
世界
74d662f7a3
Fix always create package manager 2024-06-11 21:16:57 +08:00
世界
085f603377
Bump version 2024-06-09 13:20:56 +08:00
世界
460fae83dc
Fix quic-go is caching dialErr since v0.43.0 2024-06-09 13:15:40 +08:00
世界
bb9bd9bff6
Fix package manager start order 2024-06-09 07:21:50 +08:00
世界
c2354ebf25
Bump version 2024-06-08 22:00:46 +08:00
世界
c1f4755c4e
Fix parse UDP DNS server with addr:port address 2024-06-08 22:00:46 +08:00
世界
0ca5909b06
Stop passing device sleep events on Android and Apple platforms 2024-06-08 22:00:46 +08:00
世界
e77a8114c5
Fix rule-set start order 2024-06-08 22:00:46 +08:00
renovate[bot]
f1393235ff
[dependencies] Update actions/checkout digest to a5ac7e5 2024-06-08 22:00:46 +08:00
renovate[bot]
bdba2365de
[dependencies] Update goreleaser/goreleaser-action action to v6 2024-06-08 18:58:22 +08:00
世界
ce0da5b557
Fix typo 2024-06-08 18:58:14 +08:00
世界
3853201412
Bump version 2024-06-07 19:07:46 +08:00
世界
7003ef40a3
Fix wireguard start 2024-06-07 19:07:46 +08:00
世界
59ec92228c
Fix repeatedly no route logs 2024-06-07 15:03:48 +08:00
世界
0eeb2da323
Fix reset HTTP3 DNS transport 2024-06-06 22:28:43 +08:00
世界
977b0fac02
Fix get source address from X-Forwarded-For 2024-06-06 22:21:37 +08:00
世界
51964801ff
Fix enforced power listener on windows 2024-06-06 22:20:23 +08:00
世界
e08c052fc9
Fix wireguard client bind 2024-06-06 22:20:21 +08:00
世界
53927d8bbd
Remove logs in router initialize 2024-06-06 22:20:19 +08:00
世界
968b9bc217
Fix crash on *bsd 2024-06-06 22:20:17 +08:00
lgjint
69dc87aa6d
Fix set KDE6 system proxy 2024-06-06 22:20:14 +08:00
Fei1Yang
4193df375f
build: Remove vendor in RPM packages 2024-05-27 19:28:15 +08:00
世界
5ff7006326
Bump version 2024-05-25 11:30:43 +08:00
世界
a89107ea9d
documentation: Bump version 2024-05-23 14:57:45 +08:00
世界
9ffdbba2ed
documentation: Add manuel for mitigating tunnelvision attacks 2024-05-23 14:57:27 +08:00
世界
65c71049ea
documentation: Update DNS manual 2024-05-23 14:57:26 +08:00
世界
7d4e6a7f4e
dialer: Allow nil router 2024-05-23 14:57:26 +08:00
世界
d612620c5d
Add rule-set match command 2024-05-23 14:57:26 +08:00
世界
8a9a77a438
Add bypass_domain and search_domain platform HTTP proxy options 2024-05-23 14:57:26 +08:00
世界
a2098c18e1
Handle includeAllNetworks 2024-05-23 14:57:26 +08:00
世界
cf2181dd3a
Update gVisor to 20240422.0 2024-05-23 14:57:15 +08:00
世界
5899e95ff1
Update quic-go to v0.43.1 2024-05-21 15:12:05 +08:00
世界
d7160c19cf
Fixed order for Clash modes 2024-05-21 15:12:05 +08:00
世界
da9e22b4e6
Add custom prefix support in EDNS0 client subnet options 2024-05-21 15:12:05 +08:00
气息
0e120f8a44
Fix DNS exchange index
Signed-off-by: 气息 <qdshizh@gmail.com>
2024-05-21 15:12:05 +08:00
dyhkwong
d918863ac5
Always disable cache for fake-ip servers 2024-05-21 15:12:04 +08:00
PuerNya
2ae192305c
Always disable cache for fake-ip DNS transport if independent_cache disabled 2024-05-21 15:12:03 +08:00
世界
71d1879bd6
Fix missing rule_set_ipcidr_match_source item in DNS rules 2024-05-21 15:12:03 +08:00
世界
917514e09f
Improve DNS truncate behavior 2024-05-21 15:12:03 +08:00
世界
5327aeaea4
Fix DNS fallthrough incorrectly 2024-05-21 15:12:03 +08:00
世界
93ae3f7a1e
Add rejected DNS response cache support 2024-05-21 15:12:03 +08:00
世界
f24a2aed7d
Add support for client-subnet DNS options 2024-05-21 15:12:03 +08:00
世界
0517ceef76
Add address filter support for DNS rules 2024-05-21 15:12:02 +08:00
世界
830ea46932
Fix timezone for Android and iOS 2024-05-21 15:11:52 +08:00
世界
cd0fcd5ddc
Improve loopback detector 2024-05-21 15:11:52 +08:00
世界
003176f069
Remove unused fakeip packet conn 2024-05-21 15:11:52 +08:00
世界
71d92518c1
Set the default TCP keep alive period 2024-05-21 15:11:52 +08:00
世界
b5dcd6bf59
Migrate ntp service to library 2024-05-21 15:11:52 +08:00
世界
11c7b4a866
Handle Windows power events 2024-05-21 15:11:52 +08:00
世界
ee14135298
Improve domain suffix match behavior
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.

This change modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
2024-05-21 15:11:41 +08:00
世界
cbcf005f37
Remove PROCESS_NAME_NATIVE dwFlag in process query output
The `process_path` rule of sing-box is inherited from Clash,
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
but when the device has multiple disks, the HarddiskVolume serial number is not stable.

This change make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
which will disrupt the existing `process_path` use cases in Windows.
2024-05-18 17:22:14 +08:00
世界
daee0b154e
badtls: Support uTLS and TLS ECH for read waiter 2024-05-18 17:22:14 +08:00
世界
d530c724c0
Bump version 2024-05-18 16:53:05 +08:00
世界
7f698c1104
Fix hysteria2 panic 2024-05-18 16:20:32 +08:00
renovate[bot]
7a4a44c6d2
[dependencies] Update golangci/golangci-lint-action action to v6 2024-05-10 19:49:20 +08:00
世界
44277e5dd2
Fix urltest logic 2024-05-10 17:41:20 +08:00
世界
1f470c69c4
Update docker workflow 2024-05-03 19:33:20 +08:00
世界
742adacce7
Bump version 2024-05-03 17:38:26 +08:00
世界
32e1d5a5e2
documentation: Update package status 2024-05-03 17:38:26 +08:00
世界
cb9f4ce597
Minor fixes 2024-05-03 15:34:47 +08:00
世界
4b1a6185ba
Fix DNF repo 2024-04-29 23:13:04 +08:00
世界
8d85c92356
Fix multiplex client dialer context 2024-04-29 11:58:20 +08:00
世界
c6164c9eca
Fix usage of github actions 2024-04-29 11:58:20 +08:00
dyhkwong
3c85b8bc48
Fix fake-ip mapping 2024-04-29 11:58:20 +08:00
HystericalDragon
8b8fb4344c
Remove unused encoder 2024-04-29 11:58:20 +08:00
renovate[bot]
e85a38e059
[dependencies] Update golangci/golangci-lint-action action to v5 2024-04-29 11:58:20 +08:00
renovate[bot]
f3ac91673a
[dependencies] Update actions/checkout digest to 0ad4b8f 2024-04-29 11:58:20 +08:00
世界
0f1e58b917
documentation: Update TestFlight 2024-04-29 11:58:20 +08:00
世界
c4cfe24aef
documentation: Fix strict_route description 2024-04-29 11:58:20 +08:00
世界
3d73b159ba
Fix linux workflow 2024-04-29 11:58:20 +08:00
世界
0ae1afef44
Bump version 2024-04-23 14:14:40 +08:00
世界
a5e2a4073b
Update dependencies 2024-04-23 14:03:55 +08:00
世界
b6cb3948a3
Fix initial packet MTU for QUIC protocols 2024-04-23 11:12:31 +08:00
世界
7b0f5061dc
Fix loopback detector 2024-04-17 21:45:54 +08:00
世界
76f20482f7
Fix linux repo 2024-04-12 22:52:37 +08:00
世界
e735a5bdc8
Update dependencies 2024-04-12 22:52:37 +08:00
世界
70381e93c8
Bump version 2024-04-08 11:55:41 +08:00
世界
07a40716e8
Update dependencies 2024-04-08 11:45:00 +08:00
世界
5fea5956db
Fix linux network monitor 2024-04-08 11:45:00 +08:00
世界
d20a389043
Fix timer usage 2024-04-08 11:45:00 +08:00
世界
4a4180bde5
Fixes for QUIC protocols 2024-04-08 11:44:55 +08:00
世界
7ecb6daabb
Update dependencies 2024-03-29 04:52:06 +00:00
世界
712bdd9ae5
Fix fury release 2024-03-25 10:44:57 +08:00
世界
a3b74591a7
Fix syscall packet read waiter 2024-03-25 01:49:59 +08:00
世界
2f4abc6523
Fix canceler 2024-03-24 19:12:07 +08:00
dyhkwong
965ab075d9
Fix source_ip_is_private matching 2024-03-24 19:06:02 +08:00
世界
ed2f8b9637
Bump version 2024-03-23 20:50:45 +08:00
世界
0f71ce5120
tun: Fix GSO batch size 2024-03-22 14:54:57 +08:00
世界
f8085ab111
Fix Makefile 2024-03-21 23:32:02 +08:00
世界
f61b272cbf
Fix WireGuard client bind 2024-03-20 10:52:24 +08:00
世界
59d437b9d2
Use local legacy compiler 2024-03-19 13:53:34 +08:00
世界
a7338fdc2b
Fix DNS panic 2024-03-19 12:17:32 +08:00
Puqns67
d88860928e
Fix the installation location of service files in prebuilded package 2024-03-19 12:13:59 +08:00
世界
20a2e38f47
Fix build for F-Droid 2024-03-17 21:43:22 +08:00
世界
acd438be23
Fix fury workflow 2024-03-17 21:43:22 +08:00
世界
e27fb51b54
Bump version 2024-03-16 12:05:58 +08:00
世界
adc38b26eb
Fix Makefile 2024-03-15 18:06:37 +08:00
世界
7e943e743a
Fix darwin interface monitor 2024-03-15 18:06:37 +08:00
世界
ceffcc0ad2
platform: Improve stop on apple platforms 2024-03-15 18:06:37 +08:00
世界
fdc451f7c6
platform: Fix missing log on apple platforms 2024-03-15 18:06:37 +08:00
世界
b48c471e6a
Add repo release 2024-03-15 18:06:37 +08:00
世界
4b1fabd007
Fix lint workflow 2024-03-13 19:39:59 +08:00
世界
2b5eb1c59e
Add sponsor link to issue template 2024-03-13 19:39:59 +08:00
世界
e2d3862e64
platform: reset network on invalid power events 2024-03-13 19:39:59 +08:00
世界
4f5e7b974d
Fix crash in HTTP proxy server again 2024-03-10 16:53:58 +08:00
世界
21dedddd93
Update dependencies 2024-03-08 23:36:33 +08:00
世界
e02502bec0
Update go 1.20 2024-03-08 23:31:42 +08:00
世界
ba67633ee8
Fix missing source address in inbound logs in QUIC inbounds 2024-03-07 10:47:16 +08:00
世界
7fd9abe802
Bump version 2024-03-05 13:14:38 +08:00
世界
78a5f59202
documentation: Add link to F-Droid 2024-03-05 13:13:31 +08:00
世界
8d0da685d2
Update dependencies 2024-03-02 14:29:28 +08:00
世界
e6644f784e
Fix crash in HTTP proxy server 2024-03-02 14:28:47 +08:00
世界
2b93b74d38
Fix SO_BINDTOIFINDEX usage 2024-02-29 13:03:05 +08:00
世界
dd52c26ae1
Don't return error in WireGurad client bind 2024-02-28 15:28:38 +08:00
世界
f288e3898b
Bump version 2024-02-28 15:10:28 +08:00
世界
1bc893a73a
documentation: Update privacy policy to make Play Store happy 2024-02-28 15:10:28 +08:00
世界
7359fdf195
platform: Upgrade NDK to the latest LTS version 2024-02-28 15:10:28 +08:00
世界
02b7041de6
Update dependencies 2024-02-26 22:53:31 +08:00
世界
96ac931b11
Fix network/interface monitor 2024-02-26 22:53:31 +08:00
世界
3077a82650
Fix reproducible builds 2024-02-24 23:19:31 +08:00
世界
de998c5119
Fix docker workflow 2024-02-24 22:49:07 +08:00
世界
d32c30c4b7
documentation: Bump version 2024-02-24 13:20:43 +08:00
世界
4823023806
Remove invalid archlinux packages from release 2024-02-24 13:20:43 +08:00
Aleksandr Razumov
bb355d17b2
Add riscv64 to platform list in release 2024-02-24 13:20:43 +08:00
hiddify
aaf30bf92b
platform: Fix group update interval not taking effect 2024-02-24 13:20:43 +08:00
hiddify
f8c400cffc
platform: Remove duplicated close 2024-02-24 13:20:43 +08:00
hatune-miku
3c24411e14
documentation: Fix navigation menu 2024-02-24 13:20:42 +08:00
世界
4a44aa3c21
Fix HTTP inbound 2024-02-24 13:20:42 +08:00
世界
8db2ae0c83
Fix documentation 2024-02-24 13:20:42 +08:00
世界
80d1aebcb7
platform: Unify client versions 2024-02-24 13:20:27 +08:00
世界
5583e01c99
platform: Export NeedWIFIState for Android 2024-02-18 14:24:21 +08:00
世界
bca0b86549
Copy DNS message struct instead of deep copy 2024-02-10 23:49:09 +08:00
世界
8332878cdc
Fix destination IP CIDR match in DNS 2024-02-10 22:37:42 +08:00
世界
d0ba69ad22
Fix TUN unaligned panic on windows 2024-02-10 21:22:02 +08:00
世界
31b8834427
documentation: Fix description for with_ech 2024-02-10 12:01:09 +08:00
renovate[bot]
d0f7a59e9b
[dependencies] Update golang Docker tag to v1.22 2024-02-10 11:54:40 +08:00
renovate[bot]
71e7d517a8
[dependencies] Update github-actions 2024-02-10 11:54:31 +08:00
世界
e6885e9967
platform: Ignore momentary pause on iOS 2024-02-09 13:57:47 +08:00
世界
e2090923db
Bump Go version 2024-02-08 20:31:40 +08:00
世界
46be319976
Fix external controller crash before started 2024-02-08 20:31:40 +08:00
renovate[bot]
b27bc45cf2
[dependencies] Update actions/cache action to v4 2024-02-03 15:08:41 +08:00
世界
3d735281f4
documentation: Bump version 2024-02-02 17:51:40 +08:00
世界
8760a0d94d
Fix android interface monitor 2024-02-02 17:51:40 +08:00
世界
2239b59933
Remove duplicated rules 2024-02-02 14:30:27 +08:00
世界
425a63f59d
Fix rawConn not closed for hy/hy2 2024-02-02 14:30:27 +08:00
世界
b85725c009
urltest: Remember the last choice when there is no valid result 2024-02-02 11:27:36 +08:00
世界
17aebc56c1
Fix UDP DNS response not truncated 2024-02-01 14:43:59 +08:00
Devman
f76b21b02c
Fix mobile build on windows
gobind executable name is not exactly `gobind` on windows it's `gobind.exe`

Signed-off-by: Devman <85770917+amir-devman@users.noreply.github.com>
2024-02-01 12:07:39 +08:00
kkocdko
704545a2ec
Fix rule description header of domain_suffix
I read other rule_item_xxx.go files, they are all snake case. This description is showed on dashboard like yacd.

Signed-off-by: kkocdko <31189892+kkocdko@users.noreply.github.com>
2024-02-01 10:42:12 +08:00
dyhkwong
dc7b7afc06
Fix loop back detector 2024-02-01 10:41:38 +08:00
世界
e478d3c2dc
Update workflow 2024-01-24 12:21:18 +08:00
世界
c8318058bb
documentation: Bump version 2024-01-23 11:57:28 +08:00
世界
abca2118e7
documentation: Update geosite usage 2024-01-23 11:57:28 +08:00
世界
a8ee41715a
platform: Add service error wrapper for macOS system extension 2024-01-22 19:00:09 +08:00
世界
94f76d6671
Update dependencies 2024-01-22 14:37:21 +08:00
世界
bf6cc8903c
Fix missing write result in TFO open 2024-01-19 11:22:13 +08:00
世界
1b15e1692a
Add sponsor button 2024-01-19 11:22:13 +08:00
世界
017372db25
Fix missing loopback detect 2024-01-19 11:22:12 +08:00
世界
216a0380fe
documentation: Bump version 2024-01-16 05:50:07 +08:00
Noob Zhang
71b9e4ff17
Add network-online.target in .service files
This helps the daemon work better on IoT devices
like RaspberryPi.
According to systemd's documentation,
`network.target` means there has already been
a network manager started, but the network may
not be "up". On most PCs this does not matter
because the network will turn to "up" almost
immidiately. The IoT devices' network interface
may not be set up quickly enough, so they may
meet that the sing-box daemon is started before
network is ready, which results that sing-box
cannot find a working route. The workaround
of this is restarting sing-box daemon but it
absolutely is not the perfect solution.
As `network-online.target` must be triggered by
network manager after you configured it, I keep
`network.target` so there will be no change to
those who do not enabled proper trigger service
like `NetworkManager-wait-online.service`.

See also: https://systemd.io/NETWORK_ONLINE/
2024-01-16 05:50:07 +08:00
printfer
9b7deb5246
Enhanced light/dark mode support in mkdocs configuration 2024-01-16 05:50:07 +08:00
世界
a850a73e1a
Fix missing upstream func for read wait conn 2024-01-16 05:50:07 +08:00
世界
c4d9be9e0d
Fix rule match 2024-01-14 13:01:57 +08:00
世界
f31c604b3d
documentation: Bump version 2024-01-09 12:22:22 +08:00
世界
4c8a50a52b
Fix TLS conn cast for vision 2024-01-09 12:22:22 +08:00
世界
b326e60998
Update dependencies 2024-01-09 12:22:22 +08:00
世界
11bec79a06
documentation: Bump version 2024-01-05 11:17:49 +08:00
世界
16eff06c37
documentation: remove usages of category-companies@cn since merged into cn 2024-01-03 12:21:53 +08:00
世界
2911eba236
documentation: Update package managers 2024-01-03 12:21:48 +08:00
世界
2e607118c3
Remove unnecessary context wrappers 2024-01-03 12:21:48 +08:00
世界
89c723e3e4
Improve read wait interface &
Refactor Authenticator interface to struct &
Update smux &
Update gVisor to 20231204.0 &
Update quic-go to v0.40.1 &
Update wireguard-go &
Add GSO support for TUN/WireGuard &
Fix router pre-start &
Fix bind forwarder to interface for systems stack
2024-01-03 12:21:47 +08:00
世界
35fd9de3ff
Make generated files have SUDO_USER's permissions if possible. 2024-01-03 12:21:47 +08:00
世界
6ddcd3954d
Refactor inbound/outbound options struct 2024-01-03 12:21:47 +08:00
世界
36b0f2e91a
Improve configuration merge 2024-01-03 12:21:47 +08:00
世界
fe053e26b5
Update uTLS to 1.5.4 2024-01-03 12:21:47 +08:00
世界
269434cfe6
Update tfo-go 2024-01-03 12:21:47 +08:00
世界
88495a24dc
Update gomobile and add tag 2024-01-03 12:21:46 +08:00
世界
d131a7c10a
Update cloudflare-tls to go1.21.5 2024-01-03 12:21:46 +08:00
世界
744a5d703b
Make type check strict 2024-01-03 12:21:46 +08:00
世界
09421b6378
Remove comparable limit for Listable 2024-01-03 12:21:46 +08:00
世界
21283b554a
Avoid opening log output before start &
Replace tracing logs with task monitor
2024-01-03 12:21:46 +08:00
世界
25810b50c1
Update documentation 2024-01-03 12:21:37 +08:00
世界
f1e3a59db3
Add idle_timeout for URLTest outbound 2024-01-03 12:21:37 +08:00
世界
a99deb2cb5
Skip internal fake-ip queries 2024-01-03 12:21:37 +08:00
世界
38d28e0763
Migrate contentjson and badjson to library &
Add omitempty in format
2024-01-03 12:21:37 +08:00
世界
e09a94bb9e
Update documentation 2024-01-03 12:21:36 +08:00
世界
a21c5324fd
Independent source_ip_is_private and ip_is_private rules 2024-01-03 12:21:36 +08:00
世界
4b43acfec0
Add rule-set 2024-01-03 12:21:36 +08:00
世界
7df151e820
Update buffer usage 2024-01-03 12:21:36 +08:00
世界
5948ffb965
Allow nested logical rules 2024-01-03 12:21:36 +08:00
世界
bf4e556f67
Migrate to independent cache file 2024-01-03 12:21:36 +08:00
世界
e3f8567690
documentation: Bump version 2024-01-02 14:31:53 +08:00
世界
40c7f3e170
Fix geoip close 2024-01-02 14:31:23 +08:00
世界
c506255e0f
Fix grpc lite transport encoding 2024-01-01 16:16:58 +08:00
世界
87c6fd4c0f
Fix h2mux request context 2024-01-01 16:15:49 +08:00
世界
19c445d28e
documentation: Bump version 2023-12-29 18:00:40 +08:00
世界
9119a5209b
dcoumentation: Fix description of cipher_suites 2023-12-29 18:00:40 +08:00
世界
46c8d6e61f
Fix pprof URL path 2023-12-29 18:00:40 +08:00
世界
ea17c2786d
Update dependencies 2023-12-27 10:37:18 +08:00
世界
12ababd911
Fix mux test 2023-12-27 10:30:19 +08:00
世界
0523845833
Update issue reporting templates
Enhanced the issue reporting templates for both English and Chinese versions by adding more structured and comprehensive guideline checkboxes. This aims to ensure contributors provide sufficient and beneficial information for reproducing and resolving issues, thereby improving the quality of reports and making issue tracking more efficient.
2023-12-26 19:05:19 +08:00
renovate[bot]
57794919fa
[dependencies] Update actions/upload-artifact action to v4 2023-12-26 19:04:51 +08:00
世界
f5bb5cf343
Fix missing marshal for udp_timeout 2023-12-26 10:52:46 +08:00
世界
3eed614dea
Fix ACME ALPN conflict 2023-12-26 09:02:58 +08:00
世界
76a295a660
Fix missing nil check for URLTest 2023-12-26 09:02:58 +08:00
世界
082e3fb8df
Fix V2Ray transport path validation behavior 2023-12-26 09:02:58 +08:00
世界
a0cab4f563
Fix websocket client initialize 2023-12-22 20:38:06 +08:00
世界
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 aaa67028637f8cd1055ac8ccc56b44d08dc4c546.
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 d1fe17a4db1e444485fede62499135d95ce8c661.
2022-11-28 12:51:23 +08:00
世界
51ce672076
Fix crash when input bad method in shadowsocks multi-user inbound 2022-11-28 12:51:23 +08:00
世界
7734afc40c
Update dependencies 2022-11-28 12:51:23 +08:00
世界
ee3cd49aa5
Fix tls config for h2 server 2022-11-26 14:55:51 +08:00
世界
bf20ff84b5
Fix lint 2022-11-26 11:21:24 +08:00
世界
c58302554c
Fix documentation 2022-11-26 11:06:57 +08:00
世界
05ed88aba8
Update documentation 2022-11-25 22:59:30 +08:00
世界
9f5cc0442b
Fix dockerfile 2022-11-25 22:59:30 +08:00
世界
2641a43ad8
Remove test on pull request 2022-11-25 21:12:45 +08:00
世界
4a6ab5e9fd
Fix cancel on start 2022-11-25 21:12:45 +08:00
世界
d1fe17a4db
Fix listen packet on address 2022-11-25 21:12:45 +08:00
世界
7c910165ef
Cleanup code 2022-11-24 12:37:29 +08:00
世界
8c1fddcf8d
Remove connect packet conn 2022-11-24 12:01:25 +08:00
Hellojack
01b4769852
Cleanup gun conn code 2022-11-23 14:56:31 +08:00
世界
a401828ed5
Fix shadowtls server detection 2022-11-22 22:15:38 +08:00
世界
ffd54eef6c
Update documentation 2022-11-21 21:20:44 +08:00
世界
c16e4316d6
Fix shadowtls server 2022-11-21 21:20:44 +08:00
世界
8b7fe20b7f
Include uTLS in release 2022-11-21 15:25:49 +08:00
世界
696c1065b6
Update stable documentation 2022-11-21 14:57:22 +08:00
世界
5d690f4147
Update documentation 2022-11-21 13:18:04 +08:00
世界
f906641a82
Add uTLS to makefile default tags 2022-11-21 13:18:04 +08:00
世界
89913dfa8c
Improve shadowtls server 2022-11-21 13:18:04 +08:00
世界
468778f67f
Update dependencies 2022-11-21 13:18:04 +08:00
世界
22a22aebe2
Fix default dns transport strategy 2022-11-21 13:18:04 +08:00
世界
a2d2ec9b45
Update documentation 2022-11-15 17:36:42 +08:00
世界
2695b3516e
Update issue template 2022-11-13 11:45:24 +08:00
世界
3a9ef8fac0
Remove unused 2022-11-13 11:30:48 +08:00
世界
ebad363201
Fix create TLS config 2022-11-13 11:24:37 +08:00
世界
11076d52cd
Fix dns buffer & quic retry 2022-11-13 11:16:10 +08:00
世界
5eb132063e
Fix connect packet connection for mux client 2022-11-12 03:53:42 +08:00
世界
13ab5d3348
Remove follow in update script 2022-11-11 22:32:24 +08:00
世界
ce1ddc400f
Support x/h2 v0.2.0 deadline 2022-11-11 22:08:20 +08:00
arm64v8a
2c9d25e853
Fix websocket alpn 2022-11-11 20:01:49 +08:00
世界
3d76777760
Fix tor geoip 2022-11-10 22:42:05 +08:00
世界
24f4dfea04
Fix hysteria test 2022-11-10 21:10:18 +08:00
世界
2fc1a0a9dd
Update documentation 2022-11-10 16:33:10 +08:00
世界
617aba84e4
Add multi user support for hysteria inbound 2022-11-09 21:00:08 +08:00
世界
5510c474c7
Fix h2c transport 2022-11-09 12:15:14 +08:00
世界
eb2e8a0b40
Add custom tls client support for std grpc 2022-11-09 11:46:29 +08:00
世界
972491c19d
Fix default local DNS server behavior 2022-11-09 10:35:16 +08:00
世界
7358ca4a52
Fix vmess request buffer 2022-11-09 10:16:22 +08:00
世界
61c274045a
Update install go script 2022-11-08 23:19:53 +08:00
世界
f205140b04
Fix smux keep alive 2022-11-08 16:45:38 +08:00
世界
1db8e03c86
Fix format 2022-11-08 14:54:19 +08:00
世界
2ecf86c2bc
Update patched quic-go 2022-11-08 13:54:01 +08:00
世界
999a847e86
Add custom wireguard worker size option 2022-11-08 13:48:14 +08:00
世界
1f63ce5dee
Fix reset outbound 2022-11-06 10:36:19 +08:00
世界
0ad1bbea11
Fix wireguard close 2022-11-06 10:20:23 +08:00
世界
b2cd78d279
Move WFP manipulation to strict route 2022-11-06 10:16:07 +08:00
世界
d5bb58a0b4
Update documentation 2022-11-06 10:16:07 +08:00
世界
7f84936050
Split bind_address 2022-11-06 10:16:07 +08:00
Dreamacro
6adfea0a72
Fix macOS Ventura process name match 2022-11-06 10:16:07 +08:00
Hellojack
10f213bf3d
Adjust uTLS wrapper 2022-11-06 10:16:07 +08:00
世界
6e8c4f6576
Update documentation 2022-10-31 13:59:52 +08:00
世界
9779dc0154
Fix test 2022-10-31 13:59:52 +08:00
世界
a2abe31298
Fix uTLS config 2022-10-31 13:59:52 +08:00
世界
930d177dd0
Update dependencies 2022-10-31 13:59:52 +08:00
Fei1Yang
f3d1b59173
Update container action 2022-10-29 18:01:32 +08:00
世界
14452f3049
Update documentation 2022-10-29 18:00:05 +08:00
世界
4119c8647b
Update dependencies 2022-10-29 17:56:21 +08:00
世界
90a94a8c63
Improve local dns transport 2022-10-29 17:37:11 +08:00
世界
b0c39ac7ff
Suppress no network error 2022-10-28 09:54:04 +08:00
世界
8703e1ff98
Fix decrypt xplus packet 2022-10-28 09:53:57 +08:00
世界
35886b88d7
Add option for custom wireguard reserved bytes 2022-10-28 09:53:57 +08:00
永雏塔菲
d583b35717
Add s390x architecture support
* Update debug.yml

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

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

* Add shadowtls inbound

* Add shadowtls example

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

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

30
.fpm_openwrt Normal file
View File

@ -0,0 +1,30 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json
--depends ca-bundle
--depends kmod-inet-diag
--depends kmod-tun
--depends firewall4
--before-remove release/config/openwrt.prerm
release/config/config.json=/etc/sing-box/config.json
release/config/openwrt.conf=/etc/config/sing-box
release/config/openwrt.init=/etc/init.d/sing-box
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

25
.fpm_systemd Normal file
View File

@ -0,0 +1,25 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--config-files /etc/sing-box/config.json
--after-install release/config/sing-box.postinst
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: nekohasekai

View File

@ -1,54 +1,88 @@
name: Bug Report name: Bug report
description: "Create a report to help us improve." description: "Report sing-box bug"
labels: [ bug ]
body: body:
- type: checkboxes - type: dropdown
id: terms
attributes: attributes:
label: Welcome label: Operating system
description: Operating system type
options: options:
- label: Yes, I'm using the latest major release. Only such installations are supported. - iOS
required: true - macOS
- label: Yes, I've searched similar issues on GitHub and didn't find any. - Apple tvOS
required: true - Android
- label: Yes, I've included all information below (version, config, etc). - Windows
required: true - Linux
- Others
- type: textarea
id: problem
attributes:
label: Description of the problem
placeholder: Your problem description
validations: validations:
required: true required: true
- type: input
- type: textarea
id: version
attributes: attributes:
label: Version of sing-box label: System version
value: |- description: Please provide the operating system version
<details>
```console
$ sing-box --version
# Paste output here
```
</details>
validations: validations:
required: true required: true
- type: dropdown
- type: textarea
id: config
attributes: attributes:
label: Server and client configuration file label: Installation type
value: |- description: Please provide the sing-box installation type
<details> options:
- Original sing-box Command Line
```console - sing-box for iOS Graphical Client
# paste json here - sing-box for macOS Graphical Client
``` - sing-box for Apple tvOS Graphical Client
- sing-box for Android Graphical Client
</details> - 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: validations:
required: true required: true
- type: input
attributes:
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: |-
In addition, if you encounter a crash with the graphical client, please also 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
- type: checkboxes
id: supporter
attributes:
label: Supporter
options:
- label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: Integrity requirements
description: |-
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.
options:
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
required: true
- label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
required: true
- label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
required: true
- label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
required: true

View File

@ -0,0 +1,88 @@
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
id: supporter
attributes:
label: 支持我们
options:
- label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: 完整性要求
description: |-
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目,如果您在此处欺骗以故意浪费开发者的时间,您将被永久封锁。
options:
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
required: true
- label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
required: true
- label: 我保证提供了可用于重现我报告的错误的最简配置而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
required: true
- label: 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
required: true

28
.github/deb2ipk.sh vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
set -e -o pipefail
PROJECT=$(dirname "$0")/../..
TMP_PATH=`mktemp -d`
cp $2 $TMP_PATH
pushd $TMP_PATH
DEB_NAME=`ls *.deb`
ar x $DEB_NAME
mkdir control
pushd control
tar xf ../control.tar.gz
rm md5sums
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
cat control
tar czf ../control.tar.gz ./*
popd
DEB_NAME=${DEB_NAME%.deb}
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
popd
cp $TMP_PATH/$DEB_NAME.ipk $3
rm -r $TMP_PATH

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"
}
]
}

25
.github/setup_legacy_go.sh vendored Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
VERSION="1.23.6"
mkdir -p $HOME/go
cd $HOME/go
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_legacy
cd go_legacy
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x
# that means after golang1.24 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1

14
.github/update_clients.sh vendored Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
function updateClient() {
pushd clients/$1
git fetch
git reset FETCH_HEAD --hard
popd
git add clients/$1
}
updateClient "apple"
updateClient "android"

View File

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

667
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,667 @@
name: Build
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
build:
description: "Build type"
required: true
type: choice
default: "All"
options:
- All
- Binary
- Android
- Apple
- app-store
- iOS
- macOS
- tvOS
- macOS-standalone
- publish-android
push:
branches:
- main-next
- dev-next
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true
jobs:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go: true }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
- { os: darwin, arch: arm64 }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Cache Legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
uses: actions/cache@v4
with:
path: |
~/go/go_legacy
key: go_legacy_1236
- name: Setup Legacy Go
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
.github/setup_legacy_go.sh
- name: Setup Legacy Go 2
if: matrix.legacy_go
run: |-
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
local-cache: true
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.os != 'android'
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Android
if: matrix.os == 'android'
run: |
set -xeuo pipefail
go install -v ./cmd/internal/build
export CC='${{ matrix.ndk }}-clang'
export CXX="${CC}++"
mkdir -p dist
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
BUILD_GOOS: ${{ matrix.os }}
BUILD_GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
if [[ -n "${{ matrix.goarm }}" ]]; then
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
DIR_NAME="${DIR_NAME}-legacy"
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Package Pacman
if: matrix.pacman != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box
- name: Package OpenWrt
if: matrix.openwrt != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_openwrt .fpm
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/openwrt.deb" \
--architecture all \
dist/sing-box=/usr/bin/sing-box
for architecture in ${{ matrix.openwrt }}; do
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
done
rm "dist/openwrt.deb"
- name: Archive
run: |
set -xeuo pipefail
cd dist
mkdir -p "${DIR_NAME}"
cp ../LICENSE "${DIR_NAME}"
if [ '${{ matrix.os }}' = 'windows' ]; then
cp sing-box "${DIR_NAME}/sing-box.exe"
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
else
cp sing-box "${DIR_NAME}"
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
fi
rm -r "${DIR_NAME}"
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
build_android:
name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
runs-on: ubuntu-latest
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_android
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
- name: Gradle cache
uses: actions/cache@v4
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Update version
if: github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
- name: Update nightly version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci --nightly
- name: Build
run: |-
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
run: |-
mkdir -p dist
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
path: 'dist'
publish_android:
name: Publish Android
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
runs-on: ubuntu-latest
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_android
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
- name: Gradle cache
uses: actions/cache@v4
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
./gradlew :app:publishPlayReleaseBundle
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple:
name: Build Apple clients
runs-on: macos-15
needs:
- calculate_version
strategy:
matrix:
include:
- name: iOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
platform: ios
scheme: SFI
destination: 'generic/platform=iOS'
archive: build/SFI.xcarchive
upload: SFI/Upload.plist
- name: macOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
platform: macos
scheme: SFM
destination: 'generic/platform=macOS'
archive: build/SFM.xcarchive
upload: SFI/Upload.plist
- name: tvOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
platform: tvos
scheme: SFT
destination: 'generic/platform=tvOS'
archive: build/SFT.xcarchive
upload: SFI/Upload.plist
- name: macOS-standalone
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
platform: macos
scheme: SFM.System
destination: 'generic/platform=macOS'
archive: build/SFM.System.xcarchive
export: SFM.System/Export.plist
export_path: build/SFM.System
steps:
- name: Checkout
if: matrix.if
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag
if: matrix.if
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
cd clients/apple
git checkout dev
- name: Setup certificates
if: matrix.if
run: |-
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_PATH"
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
env:
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
ASC_KEY: ${{ secrets.ASC_KEY }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Build library
if: matrix.if
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
mv Libbox.xcframework clients/apple
- name: Update macOS version
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
run: |-
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
- name: Update version
if: matrix.if && matrix.name != 'iOS'
run: |-
go run -v ./cmd/internal/update_apple_version --ci
- name: Build
if: matrix.if
run: |-
cd clients/apple
xcodebuild archive \
-scheme "${{ matrix.scheme }}" \
-configuration Release \
-destination "${{ matrix.destination }}" \
-archivePath "${{ matrix.archive }}" \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- name: Upload to App Store Connect
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- name: Publish to TestFlight
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
run: |-
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
- name: Build image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
pushd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.export }} \
-exportPath "${{ matrix.export_path }}"
brew install create-dmg
create-dmg \
--volname "sing-box" \
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
--icon "SFM.app" 0 0 \
--hide-extension "SFM.app" \
--app-drop-link 0 0 \
--skip-jenkins \
SFM.dmg "${{ matrix.export_path }}/SFM.app"
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
mkdir -p dist
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-macos-dmg
path: 'dist'
upload:
name: Upload builds
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
runs-on: ubuntu-latest
needs:
- calculate_version
- build
- build_android
- build_apple
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Cache ghr
uses: actions/cache@v4
id: cache-ghr
with:
path: |
~/go/bin/ghr
key: ghr
- name: Setup ghr
if: steps.cache-ghr.outputs.cache-hit != 'true'
run: |-
cd $HOME
git clone https://github.com/nekohasekai/ghr ghr
cd ghr
go install -v .
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Upload builds
if: ${{ env.PUBLISHED == 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds
if: ${{ env.PUBLISHED != 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,198 +0,0 @@
name: Debug build
on:
push:
branches:
- dev
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- dev
jobs:
build:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Cancel previous
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Add cache to Go proxy
run: |
version=`git rev-parse HEAD`
mkdir build
pushd build
go mod init build
go get -v github.com/sagernet/sing-box@$version
popd
- name: Run Test
run: |
go test -v ./...
cross:
strategy:
matrix:
include:
# windows
- name: windows-amd64
goos: windows
goarch: amd64
goamd64: v1
- name: windows-amd64-v3
goos: windows
goarch: amd64
goamd64: v3
- name: windows-386
goos: windows
goarch: 386
- name: windows-arm64
goos: windows
goarch: arm64
- name: windows-arm32v7
goos: windows
goarch: arm
goarm: 7
# linux
- name: linux-amd64
goos: linux
goarch: amd64
goamd64: v1
- name: linux-amd64-v3
goos: linux
goarch: amd64
goamd64: v3
- name: linux-386
goos: linux
goarch: 386
- name: linux-arm64
goos: linux
goarch: arm64
- name: linux-armv5
goos: linux
goarch: arm
goarm: 5
- name: linux-armv6
goos: linux
goarch: arm
goarm: 6
- name: linux-armv7
goos: linux
goarch: arm
goarm: 7
- name: linux-mips-softfloat
goos: linux
goarch: mips
gomips: softfloat
- name: linux-mips-hardfloat
goos: linux
goarch: mips
gomips: hardfloat
- name: linux-mipsel-softfloat
goos: linux
goarch: mipsle
gomips: softfloat
- name: linux-mipsel-hardfloat
goos: linux
goarch: mipsle
gomips: hardfloat
- name: linux-mips64
goos: linux
goarch: mips64
- name: linux-mips64el
goos: linux
goarch: mips64le
# darwin
- name: darwin-amd64
goos: darwin
goarch: amd64
goamd64: v1
- name: darwin-amd64-v3
goos: darwin
goarch: amd64
goamd64: v3
- name: darwin-arm64
goos: darwin
goarch: arm64
# freebsd
- name: freebsd-amd64
goos: freebsd
goarch: amd64
goamd64: v1
- name: freebsd-amd64-v3
goos: freebsd
goarch: amd64
goamd64: v3
- name: freebsd-386
goos: freebsd
goarch: 386
- name: freebsd-arm64
goos: freebsd
goarch: arm64
fail-fast: false
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOAMD64: ${{ matrix.goamd64 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
CGO_ENABLED: 0
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build
id: build
run: |
VERSION="$(date +%Y%m%d).$(git rev-parse --short HEAD)"
BUILDTIME="$(LANG=en_US.UTF-8 date -u)"
go build -v -trimpath -ldflags '\
-X "github.com/sagernet/sing-box/constant.Version=$VERSION" \
-X "github.com/sagernet/sing-box/constant.BuildTime=$BUILDTIME" \
-s -w -buildid=' ./cmd/sing-box
echo "::set-output name=VERSION::$VERSION"
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: sing-box-${{ matrix.name }}-${{ steps.build.outputs.VERSION }}
path: sing-box*

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

@ -0,0 +1,133 @@
name: Publish Docker Images
on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: "The tag version you want to build"
env:
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
platform:
- linux/amd64
- linux/arm/v6
- linux/arm/v7
- linux/arm64
- linux/386
- linux/ppc64le
- linux/riscv64
- linux/s390x
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
context: .
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
if [[ $ref == *"-"* ]]; then
latest=latest-beta
else
latest=latest
fi
echo "latest=$latest"
echo "latest=$latest" >> $GITHUB_OUTPUT
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}

View File

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

184
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,184 @@
name: Build Linux Packages
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release:
types:
- published
jobs:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.3
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
local-cache: true
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set mtime
run: |-
TZ=UTC touch -t '197001010000' dist/sing-box
- name: Set name
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
run: |-
echo "NAME=sing-box" >> "$GITHUB_ENV"
- name: Set beta name
if: contains(needs.calculate_version.outputs.version, '-')
run: |-
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
- name: Set version
run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
upload:
name: Upload builds
runs-on: ubuntu-latest
needs:
- calculate_version
- build
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish packages
run: |-
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

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
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

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

@ -0,0 +1,16 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- 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
days-before-close: 5
exempt-issue-labels: 'bug,enhancement'

12
.gitignore vendored
View File

@ -1,6 +1,18 @@
/.idea/ /.idea/
/vendor/ /vendor/
/*.json /*.json
/*.srs
/*.db /*.db
/site/ /site/
/bin/ /bin/
/dist/
/sing-box
/sing-box.exe
/build/
/*.jar
/*.aar
/*.xcframework/
.DS_Store
/config.d/
/venv/

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "clients/apple"]
path = clients/apple
url = https://github.com/SagerNet/sing-box-for-apple.git
[submodule "clients/android"]
path = clients/android
url = https://github.com/SagerNet/sing-box-for-android.git

View File

@ -6,15 +6,31 @@ linters:
- gci - gci
- staticcheck - staticcheck
- paralleltest - paralleltest
- ineffassign
issues:
fix: true
linters-settings: linters-settings:
gci: gci:
custom-order: true
sections: sections:
- standard - standard
- prefix(github.com/sagernet/) - prefix(github.com/sagernet/)
- default - default
staticcheck: staticcheck:
go: '1.18' checks:
- all
- -SA1003
run:
go: "1.23"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
issues:
exclude-dirs:
- transport/simple-obfs

103
.goreleaser.fury.yaml Normal file
View File

@ -0,0 +1,103 @@
project_name: sing-box
builds:
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips64le
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
nfpms:
- &template
id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
conflicts:
- sing-box-beta
- id: package_beta
<<: *template
package_name: sing-box-beta
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
formats:
- deb
- rpm
conflicts:
- sing-box
release:
disable: true
furies:
- account: sagernet
ids:
- package
disable: "{{ not (not .Prerelease) }}"
- account: sagernet
ids:
- package_beta
disable: "{{ not .Prerelease }}"

213
.goreleaser.yaml Normal file
View File

@ -0,0 +1,213 @@
version: 2
project_name: sing-box
builds:
- &template
id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips64le
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go_legacy
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
targets:
- windows_amd64_v1
- windows_386
- id: android
<<: *template
env:
- CGO_ENABLED=1
- GOTOOLCHAIN=local
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi21-clang
- CXX=armv7a-linux-androideabi21-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android21-clang
- CXX=i686-linux-android21-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
archives:
- &template
id: archive
builds:
- main
- android
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
<<: *template
builds:
- legacy
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
nfpms:
- id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
- archlinux
# - apk
# - ipk
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
overrides:
apk:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.initd
dst: /etc/init.d/sing-box
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
ipk:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/openwrt.init
dst: /etc/init.d/sing-box
- src: release/config/openwrt.conf
dst: /etc/config/sing-box
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
checksum:
disable: true
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
signs:
- artifacts: checksum
release:
github:
owner: SagerNet
name: sing-box
draft: true
prerelease: auto
mode: replace
ids:
- archive
- package
skip_upload: true
partial:
by: target

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM --platform=$BUILDPLATFORM golang:1.24-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) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates nftables \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@ -12,3 +12,6 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.

248
Makefile Normal file
View File

@ -0,0 +1,248 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
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
build:
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
ci_build:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@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@latest
lint:
GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
proto:
@go run ./cmd/internal/protogen
@gofumpt -l -w .
@gofumpt -l -w .
proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
update_certificates:
go run ./cmd/internal/update_certificates
release:
go run ./cmd/internal/build goreleaser release --clean --skip publish
mkdir dist/release
mv dist/*.tar.gz \
dist/*.zip \
dist/*.deb \
dist/*.rpm \
dist/*_amd64.pkg.tar.zst \
dist/*_arm64.pkg.tar.zst \
dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
rm -r dist/release
release_repo:
go run ./cmd/internal/build goreleaser release -f .goreleaser.fury.yaml --clean
release_install:
go install -v github.com/tcnksm/ghr@latest
update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "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 && ./gradlew --stop
# TODO: find why and remove `-destination 'generic/platform=iOS'`
# TODO: remove xcode clean when fix control widget fixed
build_ios:
cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \
xcodebuild clean -scheme SFI && \
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
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 -allowProvisioningUpdates
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_standalone:
cd ../sing-box-for-apple && \
rm -rf build/SFM.System.xcarchive && \
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
build_macos_dmg:
rm -rf dist/SFM
mkdir -p dist/SFM
cd ../sing-box-for-apple && \
rm -rf build/SFM.System && \
rm -rf build/SFM.dmg && \
xcodebuild -exportArchive \
-archivePath "build/SFM.System.xcarchive" \
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
-exportPath "build/SFM.System" && \
create-dmg \
--volname "sing-box" \
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
--icon "SFM.app" 0 0 \
--hide-extension "SFM.app" \
--app-drop-link 0 0 \
--skip-jenkins \
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
notarize_macos_dmg:
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
--keychain-profile "notarytool-password" \
--no-s3-acceleration
upload_macos_dmg:
cd dist/SFM && \
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
upload_macos_dsyms:
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
zip -r SFM.dSYMs.zip dSYMs && \
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
popd && \
cd dist/SFM && \
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
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
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
publish_testflight:
go run -v ./cmd/internal/app_store_connect publish_testflight
prepare_app_store:
go run -v ./cmd/internal/app_store_connect prepare_app_store
publish_app_store:
go run -v ./cmd/internal/app_store_connect publish_app_store
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_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
lib:
go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
docs:
venv/bin/mkdocs serve
publish_docs:
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
docs_install:
python -m venv venv
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx

View File

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

21
adapter/certificate.go Normal file
View File

@ -0,0 +1,21 @@
package adapter
import (
"context"
"crypto/x509"
"github.com/sagernet/sing/service"
)
type CertificateStore interface {
LifecycleService
Pool() *x509.CertPool
}
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
store := service.FromContext[CertificateStore](ctx)
if store == nil {
return nil
}
return store.Pool()
}

14
adapter/connections.go Normal file
View File

@ -0,0 +1,14 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type ConnectionManager interface {
Lifecycle
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

94
adapter/dns.go Normal file
View File

@ -0,0 +1,94 @@
package adapter
import (
"context"
"net/netip"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
type DNSRouter interface {
Lifecycle
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
ClearCache()
LookupReverseMapping(ip netip.Addr) (string, bool)
ResetNetwork()
}
type DNSClient interface {
Start()
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
ClearCache()
}
type DNSQueryOptions struct {
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
if options == nil {
return &DNSQueryOptions{}, nil
}
transportManager := service.FromContext[DNSTransportManager](ctx)
transport, loaded := transportManager.Transport(options.Server)
if !loaded {
return nil, E.New("domain resolver not found: " + options.Server)
}
return &DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
}, nil
}
type RDRCStore interface {
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
SaveRDRC(transportName string, qName string, qType uint16) error
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
}
type DNSTransport interface {
Lifecycle
Type() string
Tag() string
Dependencies() []string
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}
type LegacyDNSTransport interface {
LegacyStrategy() C.DomainStrategy
LegacyClientSubnet() netip.Prefix
}
type DNSTransportRegistry interface {
option.DNSTransportOptionsRegistry
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
}
type DNSTransportManager interface {
Lifecycle
Transports() []DNSTransport
Transport(tag string) (DNSTransport, bool)
Default() DNSTransport
FakeIP() FakeIPTransport
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
}

28
adapter/endpoint.go Normal file
View File

@ -0,0 +1,28 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Endpoint interface {
Lifecycle
Type() string
Tag() string
Outbound
}
type EndpointRegistry interface {
option.EndpointOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
}
type EndpointManager interface {
Lifecycle
Endpoints() []Endpoint
Get(tag string) (Endpoint, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
}

View File

@ -0,0 +1,43 @@
package endpoint
import "github.com/sagernet/sing-box/option"
type Adapter struct {
endpointType string
endpointTag string
network []string
dependencies []string
}
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
return Adapter{
endpointType: endpointType,
endpointTag: endpointTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(endpointType, endpointTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.endpointType
}
func (a *Adapter) Tag() string {
return a.endpointTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

147
adapter/endpoint/manager.go Normal file
View File

@ -0,0 +1,147 @@
package endpoint
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.EndpointManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.EndpointRegistry
access sync.Mutex
started bool
stage adapter.StartStage
endpoints []adapter.Endpoint
endpointByTag map[string]adapter.Endpoint
}
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpointByTag: make(map[string]adapter.Endpoint),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
// started with outbound manager
return nil
}
for _, endpoint := range m.endpoints {
err := adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
endpoints := m.endpoints
m.endpoints = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, endpoint := range endpoints {
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Endpoints() []adapter.Endpoint {
m.access.Lock()
defer m.access.Unlock()
return m.endpoints
}
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
m.access.Lock()
defer m.access.Unlock()
endpoint, found := m.endpointByTag[tag]
return endpoint, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
endpoint, found := m.endpointByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.endpointByTag, tag)
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == endpoint
})
if index == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return endpoint.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
if m.started {
err = existsEndpoint.Close()
if err != nil {
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
}
}
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == existsEndpoint
})
if existsIndex == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
}
m.endpoints = append(m.endpoints, endpoint)
m.endpointByTag[tag] = endpoint
return nil
}

View File

@ -0,0 +1,72 @@
package endpoint
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.EndpointRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@ -1,31 +1,121 @@
package adapter package adapter
import ( import (
"bytes"
"context" "context"
"net" "encoding/binary"
"time"
N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/varbin"
) )
type ClashServer interface { type ClashServer interface {
Service LifecycleService
TrafficController ConnectionTracker
Mode() string
ModeList() []string
HistoryStorage() URLTestHistoryStorage
} }
type Tracker interface { type URLTestHistory struct {
Leave() Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
} }
type TrafficController interface { type URLTestHistoryStorage interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) SetHook(hook chan<- struct{})
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker) LoadURLTestHistory(tag string) *URLTestHistory
DeleteURLTestHistory(tag string)
StoreURLTestHistory(tag string, history *URLTestHistory)
Close() error
}
type V2RayServer interface {
LifecycleService
StatsService() ConnectionTracker
}
type CacheFile interface {
LifecycleService
StoreFakeIP() bool
FakeIPStorage
StoreRDRC() bool
RDRCStore
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
LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
}
type SavedBinary struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
return nil, err
}
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
if err != nil {
return nil, err
}
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return err
}
err = varbin.Read(reader, binary.BigEndian, &s.Content)
if err != nil {
return err
}
var lastUpdated int64
err = binary.Read(reader, binary.BigEndian, &lastUpdated)
if err != nil {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
if err != nil {
return err
}
return nil
} }
type OutboundGroup interface { type OutboundGroup interface {
Outbound
Now() string Now() string
All() []string All() []string
} }
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string { func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup { if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now() return group.Now()

31
adapter/fakeip.go Normal file
View File

@ -0,0 +1,31 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing/common/logger"
)
type FakeIPStore interface {
SimpleLifecycle
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 {
DNSTransport
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,27 +6,56 @@ import (
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
// Deprecated
type ConnectionHandler interface { type ConnectionHandler interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
} }
type ConnectionHandlerEx interface {
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use PacketHandlerEx instead
type PacketHandler interface { type PacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
} }
type PacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
}
// Deprecated: use OOBPacketHandlerEx instead
type OOBPacketHandler interface { type OOBPacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
} }
type OOBPacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
}
// Deprecated
type PacketConnectionHandler interface { type PacketConnectionHandler interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }
type PacketConnectionHandlerEx interface {
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use TCPConnectionHandlerEx instead
//
//nolint:staticcheck
type UpstreamHandlerAdapter interface { type UpstreamHandlerAdapter interface {
N.TCPConnectionHandler N.TCPConnectionHandler
N.UDPConnectionHandler N.UDPConnectionHandler
E.Handler E.Handler
} }
type UpstreamHandlerAdapterEx interface {
N.TCPConnectionHandlerEx
N.UDPConnectionHandlerEx
}

View File

@ -3,39 +3,112 @@ package adapter
import ( import (
"context" "context"
"net/netip" "net/netip"
"time"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-dns" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
type Inbound interface { type Inbound interface {
Service Lifecycle
Type() string Type() string
Tag() string Tag() string
} }
type TCPInjectableInbound interface {
Inbound
ConnectionHandlerEx
}
type UDPInjectableInbound interface {
Inbound
PacketConnectionHandlerEx
}
type InboundRegistry interface {
option.InboundOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
}
type InboundManager interface {
Lifecycle
Inbounds() []Inbound
Get(tag string) (Inbound, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
}
type InboundContext struct { type InboundContext struct {
Inbound string Inbound string
InboundType string InboundType string
IPVersion uint8
Network string Network string
Source M.Socksaddr Source M.Socksaddr
Destination M.Socksaddr Destination M.Socksaddr
Domain string
Protocol string
User string User string
Outbound string Outbound string
// sniffer
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
// cache // cache
DomainStrategy dns.DomainStrategy // Deprecated: implement in rule action
SniffEnabled bool InboundDetour string
SniffOverrideDestination bool LastInbound string
DestinationAddresses []netip.Addr OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
// Deprecated: to be removed
//nolint:staticcheck
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
TLSRecordFragment bool
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DestinationAddresses []netip.Addr
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string
ProcessInfo *process.Info ProcessInfo *process.Info
QueryType uint16
FakeIP bool
// rule cache
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
}
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
c.DidMatch = false
} }
type inboundContextKey struct{} type inboundContextKey struct{}
@ -52,11 +125,19 @@ func ContextFrom(ctx context.Context) *InboundContext {
return metadata.(*InboundContext) return metadata.(*InboundContext)
} }
func AppendContext(ctx context.Context) (context.Context, *InboundContext) { func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
metadata := ContextFrom(ctx) var newMetadata InboundContext
if metadata != nil { if metadata := ContextFrom(ctx); metadata != nil {
return ctx, metadata newMetadata = *metadata
} }
metadata = new(InboundContext) return WithContext(ctx, &newMetadata), &newMetadata
return WithContext(ctx, metadata), metadata }
func OverrideContext(ctx context.Context) context.Context {
if metadata := ContextFrom(ctx); metadata != nil {
var newMetadata InboundContext
newMetadata = *metadata
return WithContext(ctx, &newMetadata)
}
return ctx
} }

View File

@ -0,0 +1,21 @@
package inbound
type Adapter struct {
inboundType string
inboundTag string
}
func NewAdapter(inboundType string, inboundTag string) Adapter {
return Adapter{
inboundType: inboundType,
inboundTag: inboundTag,
}
}
func (a *Adapter) Type() string {
return a.inboundType
}
func (a *Adapter) Tag() string {
return a.inboundTag
}

149
adapter/inbound/manager.go Normal file
View File

@ -0,0 +1,149 @@
package inbound
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.InboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.InboundRegistry
endpoint adapter.EndpointManager
access sync.Mutex
started bool
stage adapter.StartStage
inbounds []adapter.Inbound
inboundByTag map[string]adapter.Inbound
}
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
inboundByTag: make(map[string]adapter.Inbound),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
inbounds := m.inbounds
m.access.Unlock()
for _, inbound := range inbounds {
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
inbounds := m.inbounds
m.inbounds = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, inbound := range inbounds {
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Inbounds() []adapter.Inbound {
m.access.Lock()
defer m.access.Unlock()
return m.inbounds
}
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
m.access.Unlock()
if found {
return inbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.inboundByTag, tag)
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == inbound
})
if index == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return inbound.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
}
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
if m.started {
err = existsInbound.Close()
if err != nil {
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
}
}
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == existsInbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
}
m.inbounds = append(m.inbounds, inbound)
m.inboundByTag[tag] = inbound
return nil
}

View File

@ -0,0 +1,72 @@
package inbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.InboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

69
adapter/lifecycle.go Normal file
View File

@ -0,0 +1,69 @@
package adapter
import E "github.com/sagernet/sing/common/exceptions"
type SimpleLifecycle interface {
Start() error
Close() error
}
type StartStage uint8
const (
StartStateInitialize StartStage = iota
StartStateStart
StartStatePostStart
StartStateStarted
)
var ListStartStages = []StartStage{
StartStateInitialize,
StartStateStart,
StartStatePostStart,
StartStateStarted,
}
func (s StartStage) String() string {
switch s {
case StartStateInitialize:
return "initialize"
case StartStateStart:
return "start"
case StartStatePostStart:
return "post-start"
case StartStateStarted:
return "finish-start"
default:
panic("unknown stage")
}
}
type Lifecycle interface {
Start(stage StartStage) error
Close() error
}
type LifecycleService interface {
Name() string
Lifecycle
}
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return err
}
}
return nil
}
func StartNamed(stage StartStage, services []LifecycleService) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
}
return nil
}

View File

@ -0,0 +1,52 @@
package adapter
func LegacyStart(starter any, stage StartStage) error {
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
return lifecycle.Start(stage)
}
switch stage {
case StartStateInitialize:
if preStarter, isPreStarter := starter.(interface {
PreStart() error
}); isPreStarter {
return preStarter.PreStart()
}
case StartStateStart:
if starter, isStarter := starter.(interface {
Start() error
}); isStarter {
return starter.Start()
}
case StartStateStarted:
if postStarter, isPostStarter := starter.(interface {
PostStart() error
}); isPostStarter {
return postStarter.PostStart()
}
}
return nil
}
type lifecycleServiceWrapper struct {
SimpleLifecycle
name string
}
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
return &lifecycleServiceWrapper{
SimpleLifecycle: service,
name: name,
}
}
func (l *lifecycleServiceWrapper) Name() string {
return l.name
}
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
return LegacyStart(l.SimpleLifecycle, stage)
}
func (l *lifecycleServiceWrapper) Close() error {
return l.SimpleLifecycle.Close()
}

57
adapter/network.go Normal file
View File

@ -0,0 +1,57 @@
package adapter
import (
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
)
type NetworkManager interface {
Lifecycle
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultNetworkInterface() *NetworkInterface
NetworkInterfaces() []NetworkInterface
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
ProtectFunc() control.Func
DefaultOptions() NetworkOptions
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
WIFIState() WIFIState
ResetNetwork()
UpdateWIFIState()
}
type NetworkOptions struct {
BindInterface string
RoutingMark uint32
DomainResolver string
DomainResolveOptions DNSQueryOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}
type NetworkInterface struct {
control.Interface
Type C.InterfaceType
DNSServers []string
Expensive bool
Constrained bool
}

View File

@ -2,8 +2,9 @@ package adapter
import ( import (
"context" "context"
"net"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -13,7 +14,20 @@ type Outbound interface {
Type() string Type() string
Tag() string Tag() string
Network() []string Network() []string
Dependencies() []string
N.Dialer N.Dialer
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error }
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
type OutboundRegistry interface {
option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
}
type OutboundManager interface {
Lifecycle
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
Default() Outbound
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
} }

View File

@ -0,0 +1,45 @@
package outbound
import (
"github.com/sagernet/sing-box/option"
)
type Adapter struct {
outboundType string
outboundTag string
network []string
dependencies []string
}
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
return Adapter{
outboundType: outboundType,
outboundTag: outboundTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(outboundType, outboundTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.outboundType
}
func (a *Adapter) Tag() string {
return a.outboundTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

287
adapter/outbound/manager.go Normal file
View File

@ -0,0 +1,287 @@
package outbound
import (
"context"
"io"
"os"
"strings"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
var _ adapter.OutboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.OutboundRegistry
endpoint adapter.EndpointManager
defaultTag string
access sync.RWMutex
started bool
stage adapter.StartStage
outbounds []adapter.Outbound
outboundByTag map[string]adapter.Outbound
dependByTag map[string][]string
defaultOutbound adapter.Outbound
defaultOutboundFallback adapter.Outbound
}
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
defaultTag: defaultTag,
outboundByTag: make(map[string]adapter.Outbound),
dependByTag: make(map[string][]string),
}
}
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
m.defaultOutboundFallback = defaultOutboundFallback
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
outbounds := m.outbounds
m.access.Unlock()
if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultOutbound == nil {
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
if !loaded {
return E.New("default outbound not found: ", m.defaultTag)
}
m.defaultOutbound = defaultEndpoint
}
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
} else {
for _, outbound := range outbounds {
err := adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
}
}
return nil
}
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
monitor := taskmonitor.New(m.logger, C.StartTimeout)
started := make(map[string]bool)
for {
canContinue := false
startOne:
for _, outboundToStart := range outbounds {
outboundTag := outboundToStart.Tag()
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.(adapter.Lifecycle); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
if err != nil {
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
} else if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
}
}
if len(started) == len(outbounds) {
break
}
if canContinue {
continue
}
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
return !started[it.Tag()]
})
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)
}
m.access.Lock()
problemOutbound := m.outboundByTag[problemOutboundTag]
m.access.Unlock()
if problemOutbound == nil {
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
}
return nil
}
func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
m.access.Lock()
if !m.started {
m.access.Unlock()
return nil
}
m.started = false
outbounds := m.outbounds
m.outbounds = nil
m.access.Unlock()
var err error
for _, outbound := range outbounds {
if closer, isCloser := outbound.(io.Closer); isCloser {
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
})
monitor.Finish()
}
}
return nil
}
func (m *Manager) Outbounds() []adapter.Outbound {
m.access.RLock()
defer m.access.RUnlock()
return m.outbounds
}
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
m.access.RLock()
outbound, found := m.outboundByTag[tag]
m.access.RUnlock()
if found {
return outbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Default() adapter.Outbound {
m.access.RLock()
defer m.access.RUnlock()
if m.defaultOutbound != nil {
return m.defaultOutbound
} else {
return m.defaultOutboundFallback
}
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
defer m.access.Unlock()
outbound, found := m.outboundByTag[tag]
if !found {
return os.ErrInvalid
}
delete(m.outboundByTag, tag)
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == outbound
})
if index == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
started := m.started
if m.defaultOutbound == outbound {
if len(m.outbounds) > 0 {
m.defaultOutbound = m.outbounds[0]
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
} else {
m.defaultOutbound = nil
}
}
dependBy := m.dependByTag[tag]
if len(dependBy) > 0 {
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
}
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
if len(m.dependByTag[dependency]) == 1 {
delete(m.dependByTag, dependency)
} else {
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
return it != tag
})
}
}
if started {
return common.Close(outbound)
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
if tag == "" {
return os.ErrInvalid
}
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
if err != nil {
return err
}
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
}
}
m.access.Lock()
defer m.access.Unlock()
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
if m.started {
err = common.Close(existsOutbound)
if err != nil {
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
}
}
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == existsOutbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
}
m.outbounds = append(m.outbounds, outbound)
m.outboundByTag[tag] = outbound
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
}
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
m.defaultOutbound = outbound
if m.started {
m.logger.Info("updated default outbound to ", outbound.Tag())
}
}
return nil
}

View File

@ -0,0 +1,72 @@
package outbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.OutboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
}

1
adapter/prestart.go Normal file
View File

@ -0,0 +1 @@
package adapter

View File

@ -2,55 +2,111 @@ package adapter
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
"net/netip" "net/http"
"sync"
"github.com/sagernet/sing-box/common/geoip" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-dns" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/x/list"
"golang.org/x/net/dns/dnsmessage" "go4.org/netipx"
) )
type Router interface { type Router interface {
Service Lifecycle
ConnectionRouter
PreMatch(metadata InboundContext) error
ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool
Rules() []Rule
AppendTracker(tracker ConnectionTracker)
ResetNetwork()
}
Outbounds() []Outbound type ConnectionTracker interface {
Outbound(tag string) (Outbound, bool) RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
DefaultOutbound(network string) Outbound RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
}
// Deprecated: Use ConnectionRouterEx instead.
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
InterfaceBindManager() control.BindManager
DefaultInterface() string
AutoDetectInterface() bool
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
Rules() []Rule
SetTrafficController(controller TrafficController)
} }
type Rule interface { type ConnectionRouterEx interface {
Service ConnectionRouter
Type() string RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
UpdateGeosite() error RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
Match(metadata *InboundContext) bool
Outbound() string
String() string
} }
type DNSRule interface { type RuleSet interface {
Rule Name() string
DisableCache() bool StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Metadata() RuleSetMetadata
ExtractIPSet() []*netipx.IPSet
IncRef()
DecRef()
Cleanup()
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
Close() error
HeadlessRule
}
type RuleSetUpdateCallback func(it RuleSet)
type RuleSetMetadata struct {
ContainsProcessRule bool
ContainsWIFIRule bool
ContainsIPCIDRRule bool
}
type HTTPStartContext struct {
ctx context.Context
access sync.Mutex
httpClientCache map[string]*http.Client
}
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
return &HTTPStartContext{
ctx: ctx,
httpClientCache: make(map[string]*http.Client),
}
}
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
c.access.Lock()
defer c.access.Unlock()
if httpClient, loaded := c.httpClientCache[detour]; loaded {
return httpClient
}
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: C.TCPTimeout,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(c.ctx),
RootCAs: RootPoolFromContext(c.ctx),
},
},
}
c.httpClientCache[detour] = httpClient
return httpClient
}
func (c *HTTPStartContext) Close() {
c.access.Lock()
defer c.access.Unlock()
for _, client := range c.httpClientCache {
client.CloseIdleConnections()
}
} }

37
adapter/rule.go Normal file
View File

@ -0,0 +1,37 @@
package adapter
import (
C "github.com/sagernet/sing-box/constant"
)
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
HeadlessRule
SimpleLifecycle
Type() string
Action() RuleAction
}
type DNSRule interface {
Rule
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleAction interface {
Type() string
String() string
}
func IsFinalAction(action RuleAction) bool {
switch action.Type() {
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
return false
default:
return true
}
}

View File

@ -1,12 +1,27 @@
package adapter package adapter
import "io" import (
"context"
type Starter interface { "github.com/sagernet/sing-box/log"
Start() error "github.com/sagernet/sing-box/option"
} )
type Service interface { type Service interface {
Starter Lifecycle
io.Closer Type() string
Tag() string
}
type ServiceRegistry interface {
option.ServiceOptionsRegistry
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
}
type ServiceManager interface {
Lifecycle
Services() []Service
Get(tag string) (Service, bool)
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
} }

View File

@ -0,0 +1,21 @@
package service
type Adapter struct {
serviceType string
serviceTag string
}
func NewAdapter(serviceType string, serviceTag string) Adapter {
return Adapter{
serviceType: serviceType,
serviceTag: serviceTag,
}
}
func (a *Adapter) Type() string {
return a.serviceType
}
func (a *Adapter) Tag() string {
return a.serviceTag
}

144
adapter/service/manager.go Normal file
View File

@ -0,0 +1,144 @@
package service
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.ServiceManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.ServiceRegistry
access sync.Mutex
started bool
stage adapter.StartStage
services []adapter.Service
serviceByTag map[string]adapter.Service
}
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
serviceByTag: make(map[string]adapter.Service),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
services := m.services
m.access.Unlock()
for _, service := range services {
err := adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
services := m.services
m.services = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, service := range services {
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Services() []adapter.Service {
m.access.Lock()
defer m.access.Unlock()
return m.services
}
func (m *Manager) Get(tag string) (adapter.Service, bool) {
m.access.Lock()
service, found := m.serviceByTag[tag]
m.access.Unlock()
return service, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
service, found := m.serviceByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.serviceByTag, tag)
index := common.Index(m.services, func(it adapter.Service) bool {
return it == service
})
if index == -1 {
panic("invalid service index")
}
m.services = append(m.services[:index], m.services[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return service.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
}
}
if existsService, loaded := m.serviceByTag[tag]; loaded {
if m.started {
err = existsService.Close()
if err != nil {
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
}
}
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
return it == existsService
})
if existsIndex == -1 {
panic("invalid service index")
}
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
}
m.services = append(m.services, service)
m.serviceByTag[tag] = service
return nil
}

View File

@ -0,0 +1,72 @@
package service
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.ServiceRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

18
adapter/ssm.go Normal file
View File

@ -0,0 +1,18 @@
package adapter
import (
"net"
N "github.com/sagernet/sing/common/network"
)
type ManagedSSMServer interface {
Inbound
SetTracker(tracker SSMTracker)
UpdateUsers(users []string, uPSKs []string) error
}
type SSMTracker interface {
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
}

8
adapter/time.go Normal file
View File

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

View File

@ -4,90 +4,165 @@ import (
"context" "context"
"net" "net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
type ( type (
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
) )
func NewUpstreamHandler( func NewUpstreamHandlerEx(
metadata InboundContext, metadata InboundContext,
connectionHandler ConnectionHandlerFunc, connectionHandler ConnectionHandlerFuncEx,
packetHandler PacketConnectionHandlerFunc, packetHandler PacketConnectionHandlerFuncEx,
errorHandler E.Handler, ) UpstreamHandlerAdapterEx {
) UpstreamHandlerAdapter { return &myUpstreamHandlerWrapperEx{
return &myUpstreamHandlerWrapper{
metadata: metadata, metadata: metadata,
connectionHandler: connectionHandler, connectionHandler: connectionHandler,
packetHandler: packetHandler, packetHandler: packetHandler,
errorHandler: errorHandler,
} }
} }
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
type myUpstreamHandlerWrapper struct { type myUpstreamHandlerWrapperEx struct {
metadata InboundContext metadata InboundContext
connectionHandler ConnectionHandlerFunc connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFunc packetHandler PacketConnectionHandlerFuncEx
errorHandler E.Handler
} }
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
w.metadata.Destination = metadata.Destination myMetadata := w.metadata
return w.connectionHandler(ctx, conn, w.metadata) if source.IsValid() {
myMetadata.Source = source
}
if destination.IsValid() {
myMetadata.Destination = destination
}
w.connectionHandler(ctx, conn, myMetadata, onClose)
} }
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
w.metadata.Destination = metadata.Destination myMetadata := w.metadata
return w.packetHandler(ctx, conn, w.metadata) if source.IsValid() {
myMetadata.Source = source
}
if destination.IsValid() {
myMetadata.Destination = destination
}
w.packetHandler(ctx, conn, myMetadata, onClose)
} }
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
w.errorHandler.NewError(ctx, err)
type myUpstreamContextHandlerWrapperEx struct {
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
} }
func UpstreamMetadata(metadata InboundContext) M.Metadata { func NewUpstreamContextHandlerEx(
return M.Metadata{ connectionHandler ConnectionHandlerFuncEx,
Source: metadata.Source, packetHandler PacketConnectionHandlerFuncEx,
Destination: metadata.Destination, ) UpstreamHandlerAdapterEx {
} return &myUpstreamContextHandlerWrapperEx{
}
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler, connectionHandler: connectionHandler,
packetHandler: packetHandler, packetHandler: packetHandler,
errorHandler: errorHandler,
} }
} }
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
myMetadata.Destination = metadata.Destination if source.IsValid() {
return w.connectionHandler(ctx, conn, *myMetadata) myMetadata.Source = source
}
if destination.IsValid() {
myMetadata.Destination = destination
}
w.connectionHandler(ctx, conn, *myMetadata, onClose)
} }
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
myMetadata.Destination = metadata.Destination if source.IsValid() {
return w.packetHandler(ctx, conn, *myMetadata) myMetadata.Source = source
}
if destination.IsValid() {
myMetadata.Destination = destination
}
w.packetHandler(ctx, conn, *myMetadata, onClose)
} }
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { func NewRouteHandlerEx(
w.errorHandler.NewError(ctx, err) metadata InboundContext,
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeHandlerWrapperEx{
metadata: metadata,
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
type routeHandlerWrapperEx struct {
metadata InboundContext
router ConnectionRouterEx
}
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
}
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
}
func NewRouteContextHandlerEx(
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeContextHandlerWrapperEx{
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
type routeContextHandlerWrapperEx struct {
router ConnectionRouterEx
}
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
}
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
} }

234
adapter/upstream_legacy.go Normal file
View File

@ -0,0 +1,234 @@
package adapter
import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
// Deprecated
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
// Deprecated
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
)
// Deprecated
//
//nolint:staticcheck
func NewUpstreamHandler(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
// Deprecated: use myUpstreamHandlerWrapperEx instead.
//
//nolint:staticcheck
type myUpstreamHandlerWrapper struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) 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.connectionHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) 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.packetHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: removed
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
Destination: metadata.Destination,
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) 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.connectionHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) 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.packetHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
//
//nolint:staticcheck
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
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)
}
// Deprecated: Use ConnectionRouterEx instead.
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)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
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)
}
// Deprecated: Use ConnectionRouterEx instead.
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)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

24
adapter/v2ray.go Normal file
View File

@ -0,0 +1,24 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type V2RayServerTransport interface {
Network() []string
Serve(listener net.Listener) error
ServePacket(listener net.PacketConn) error
Close() error
}
type V2RayServerTransportHandler interface {
N.TCPConnectionHandlerEx
}
type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error)
Close() error
}

552
box.go
View File

@ -2,194 +2,494 @@ package box
import ( import (
"context" "context"
"fmt"
"io" "io"
"os" "os"
"runtime/debug"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
) )
var _ adapter.Service = (*Box)(nil) var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct { type Box struct {
createdAt time.Time createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory logFactory log.Factory
logger log.ContextLogger logger log.ContextLogger
logFile *os.File network *route.NetworkManager
clashServer adapter.ClashServer endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{} done chan struct{}
} }
func New(ctx context.Context, options option.Options) (*Box, error) { type Options struct {
createdAt := time.Now() option.Options
logOptions := common.PtrValueOrDefault(options.Log) Context context.Context
PlatformLogWriter log.PlatformWriter
var needClashAPI bool
if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true
} }
var logFactory log.Factory func Context(
var observableLogFactory log.ObservableFactory ctx context.Context,
var logFile *os.File inboundRegistry adapter.InboundRegistry,
if logOptions.Disabled { outboundRegistry adapter.OutboundRegistry,
observableLogFactory = log.NewNOPFactory() endpointRegistry adapter.EndpointRegistry,
logFactory = observableLogFactory dnsTransportRegistry adapter.DNSTransportRegistry,
} else { serviceRegistry adapter.ServiceRegistry,
var logWriter io.Writer ) context.Context {
switch logOptions.Output { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
case "", "stderr": service.FromContext[adapter.InboundRegistry](ctx) == nil {
logWriter = os.Stderr ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
case "stdout": ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
logWriter = os.Stdout }
default: if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
var err error service.FromContext[adapter.OutboundRegistry](ctx) == nil {
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
}
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
}
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
}
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
}
return ctx
}
func New(options Options) (*Box, error) {
createdAt := time.Now()
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
}
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
if dnsTransportRegistry == nil {
return nil, E.New("missing DNS transport registry in context")
}
if serviceRegistry == nil {
return nil, E.New("missing service registry in context")
}
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
var needClashAPI bool
var needV2RayAPI bool
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
needCacheFile = true
}
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
needClashAPI = true
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
platformInterface := service.FromContext[platform.Interface](ctx)
var defaultLogWriter io.Writer
if 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")
}
var internalServices []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
logFormatter := log.Formatter{ internalServices = append(internalServices, certificateStore)
BaseTime: createdAt,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
logFactory.SetLevel(logLevel)
} else {
logFactory.SetLevel(log.LevelTrace)
}
} }
router, err := route.NewRouter( routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
if err != nil {
return nil, E.Cause(err, "initialize network manager")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, transportOptions := range dnsOptions.Servers {
var tag string
if transportOptions.Tag != "" {
tag = transportOptions.Tag
} else {
tag = F.ToString(i)
}
err = dnsTransportManager.Create(
ctx, ctx,
logFactory.NewLogger("router"), logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
logFactory.NewLogger("dns"), tag,
common.PtrValueOrDefault(options.Route), transportOptions.Type,
common.PtrValueOrDefault(options.DNS), transportOptions.Options,
options.Inbounds,
) )
if err != nil { if err != nil {
return nil, E.Cause(err, "parse route options") return nil, E.Cause(err, "initialize DNS server[", i, "]")
}
}
err = dnsRouter.Initialize(dnsOptions.Rules)
if err != nil {
return nil, E.Cause(err, "initialize dns router")
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
tag = endpointOptions.Tag
} else {
tag = F.ToString(i)
}
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create(
endpointCtx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
endpointOptions.Type,
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize endpoint[", i, "]")
}
} }
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds { for i, inboundOptions := range options.Inbounds {
var in adapter.Inbound
var tag string var tag string
if inboundOptions.Tag != "" { if inboundOptions.Tag != "" {
tag = inboundOptions.Tag tag = inboundOptions.Tag
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
in, err = inbound.New( err = inboundManager.Create(
ctx, ctx,
router, router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions, tag,
inboundOptions.Type,
inboundOptions.Options,
) )
if err != nil { if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]") return nil, E.Cause(err, "initialize inbound[", i, "]")
} }
inbounds = append(inbounds, in)
} }
for i, outboundOptions := range options.Outbounds { for i, outboundOptions := range options.Outbounds {
var out adapter.Outbound
var tag string var tag string
if outboundOptions.Tag != "" { if outboundOptions.Tag != "" {
tag = outboundOptions.Tag tag = outboundOptions.Tag
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
out, err = outbound.New( outboundCtx := ctx
ctx, if tag != "" {
// TODO: remove this
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = outboundManager.Create(
outboundCtx,
router, router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
outboundOptions) tag,
outboundOptions.Type,
outboundOptions.Options,
)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]") return nil, E.Cause(err, "initialize outbound[", i, "]")
} }
outbounds = append(outbounds, out)
} }
err = router.Initialize(outbounds, func() adapter.Outbound { for i, serviceOptions := range options.Services {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"}) var tag string
common.Must(oErr) if serviceOptions.Tag != "" {
outbounds = append(outbounds, out) tag = serviceOptions.Tag
return out } else {
}) tag = F.ToString(i)
}
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "initialize service[", i, "]")
}
}
outboundManager.Initialize(common.Must1(
direct.NewOutbound(
ctx,
router,
logFactory.NewLogger("outbound/direct"),
"direct",
option.DirectOutboundOptions{},
),
))
dnsTransportManager.Initialize(common.Must1(
local.NewTransport(
ctx,
logFactory.NewLogger("dns/local"),
"local",
option.LocalDNSServerOptions{},
)))
if platformInterface != nil {
err = platformInterface.Initialize(networkManager)
if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}
}
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
internalServices = append(internalServices, cacheFile)
} }
var clashServer adapter.ClashServer
if needClashAPI { 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, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "create clash api server") return nil, E.Cause(err, "create clash-server")
} }
router.SetTrafficController(clashServer) router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, 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-server")
}
if v2rayServer.StatsService() != nil {
router.AppendTracker(v2rayServer.StatsService())
internalServices = append(internalServices, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
if err != nil {
return nil, E.Cause(err, "create NTP service")
}
ntpService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
Server: ntpOptions.ServerOptions.Build(),
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
} }
return &Box{ return &Box{
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router, router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.NewLogger(""), logger: logFactory.Logger(),
logFile: logFile, internalService: internalServices,
clashServer: clashServer,
done: make(chan struct{}), done: make(chan struct{}),
}, nil }, nil
} }
func (s *Box) PreStart() error {
err := s.preStart()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
println(err.Error())
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) Start() error { func (s *Box) Start() error {
err := s.router.Start() err := s.start()
if err != nil { if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
println(err.Error())
debug.PrintStack()
println("panic on early start: " + fmt.Sprint(v))
}
}()
s.Close()
return err return err
} }
for i, in := range s.inbounds {
err = in.Start()
if err != nil {
for g := 0; g < i; g++ {
s.inbounds[g].Close()
}
return err
}
}
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)") s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil return nil
} }
func (s *Box) preStart() error {
monitor := taskmonitor.New(s.logger, C.StartTimeout)
monitor.Start("start logger")
err := s.logFactory.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start logger")
}
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
return nil
}
func (s *Box) start() error {
err := s.preStart()
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
if err != nil {
return err
}
return nil
}
func (s *Box) Close() error { func (s *Box) Close() error {
select { select {
case <-s.done: case <-s.done:
@ -197,16 +497,32 @@ func (s *Box) Close() error {
default: default:
close(s.done) close(s.done)
} }
for _, in := range s.inbounds { err := common.Close(
in.Close() s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
}
for _, out := range s.outbounds {
common.Close(out)
}
return common.Close(
s.router,
s.logFactory,
s.clashServer,
common.PtrOrNil(s.logFile),
) )
for _, lifecycleService := range s.internalService {
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})
}
err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger")
})
return err
}
func (s *Box) Network() adapter.NetworkManager {
return s.network
}
func (s *Box) Router() adapter.Router {
return s.router
}
func (s *Box) Inbound() adapter.InboundManager {
return s.inbound
}
func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
} }

1
clients/android Submodule

@ -0,0 +1 @@
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0

1
clients/apple Submodule

@ -0,0 +1 @@
Subproject commit ae5818ee5a24af965dc91f80bffa16e1e6c109c1

View File

@ -0,0 +1,450 @@
package main
import (
"context"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_testflight":
err := publishTestflight(ctx)
if err != nil {
log.Fatal(err)
}
case "cancel_app_store":
err := cancelAppStore(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
case "prepare_app_store":
err := prepareAppStore(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_app_store":
err := publishAppStore(ctx)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("unknown action: ", os.Args[1])
}
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
)
func createClient(expireDuration time.Duration) *asc.Client {
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
if err != nil {
log.Fatal(err)
}
return asc.NewClient(tokenConfig.Client())
}
func fetchMacOSVersion(ctx context.Context) error {
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
return nil
}
func publishTestflight(ctx context.Context) error {
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
tag := tagVersion.VersionString()
client := createClient(20 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
if err != nil {
return err
}
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
return it.ID
})
var platforms []asc.Platform
if len(os.Args) == 3 {
switch os.Args[2] {
case "ios":
platforms = []asc.Platform{asc.PlatformIOS}
case "macos":
platforms = []asc.Platform{asc.PlatformMACOS}
case "tvos":
platforms = []asc.Platform{asc.PlatformTVOS}
default:
return E.New("unknown platform: ", os.Args[2])
}
} else {
platforms = []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
}
}
for _, platform := range platforms {
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
F.ToString("sing-box ", tagVersion.String()),
))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}
break
}
}
return nil
}
func cancelAppStore(ctx context.Context, platform string) error {
switch platform {
case "ios":
platform = string(asc.PlatformIOS)
case "macos":
platform = string(asc.PlatformMACOS)
case "tvos":
platform = string(asc.PlatformTVOS)
}
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for {
log.Info(platform, " list versions")
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if isRetryable(response) {
continue
} else if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
if version.ID == "" {
return nil
}
log.Info(platform, " ", tag, " get submission")
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
}
if isRetryable(response) {
continue
} else if err != nil {
return err
}
log.Info(platform, " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
return nil
}
}
func prepareAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
log.Info(string(platform), " ", tag, " list builds")
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
log.Info(string(platform), " ", tag, " create version")
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
Platform: platform,
VersionString: tag,
}, appID, buildID)
if err != nil {
return err
}
version = newVersion.Data
} else {
log.Info(string(platform), " ", tag, " check build")
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
if submission != nil {
log.Info(string(platform), " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
time.Sleep(5 * time.Second)
}
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
log.Info(string(platform), " ", tag, " update build")
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
response.Write(os.Stderr)
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
}
} else {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
continue
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
}
}
log.Info(string(platform), " ", tag, " list localization")
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " create submission")
fixSubmit:
for {
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
if err != nil {
switch response.StatusCode {
case http.StatusInternalServerError:
continue
default:
return err
}
}
switch response.StatusCode {
case http.StatusCreated:
break fixSubmit
default:
return err
}
}
}
return nil
}
func publishAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
log.Fatal(string(platform), " ", tag, " not submitted")
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview:
log.Warn(string(platform), " ", tag, " waiting for review")
continue
case asc.AppStoreVersionStatePendingDeveloperRelease:
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
if err != nil {
return err
}
}
return nil
}
func isRetryable(response *asc.Response) bool {
if response == nil {
return false
}
switch response.StatusCode {
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
return true
default:
return false
}
}

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,187 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"strings"
_ "github.com/sagernet/gomobile"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
)
var (
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
}
func main() {
flag.Parse()
build_shared.FindMobile()
switch target {
case "android":
buildAndroid()
case "apple":
buildApple()
}
}
var (
sharedFlags []string
debugFlags []string
sharedTags []string
iosTags []string
memcTags []string
debugTags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-buildvcs=false")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
memcTags = append(memcTags, "with_tailscale")
debugTags = append(debugTags, "debug")
}
func buildAndroid() {
build_shared.FindSDK()
var javaPath string
javaHome := os.Getenv("JAVA_HOME")
if javaHome == "" {
javaPath = "java"
} else {
javaPath = filepath.Join(javaHome, "bin", "java")
}
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "android/arm64"
} else {
bindTarget = "android"
}
args := []string{
"bind",
"-v",
"-target", bindTarget,
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
tags := append(sharedTags, memcTags...)
if debugEnabled {
tags = append(tags, debugTags...)
}
args = append(args, "-tags", strings.Join(tags, ","))
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.IsDir(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 buildApple() {
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,tvos,macos"
}
args := []string{
"bind",
"-v",
"-target", bindTarget,
"-libname=box",
"-tags-macos=" + strings.Join(memcTags, ","),
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
tags := append(sharedTags, iosTags...)
if debugEnabled {
tags = append(tags, debugTags...)
}
args = append(args, "-tags", strings.Join(tags, ","))
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.IsDir(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,106 @@
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.IsFile(filepath.Join(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 {
const fixedVersion = "28.0.13004108"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath
return true
}
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
androidNDKPath = ndkHomeEnv
return true
}
ndkVersions, err := os.ReadDir(filepath.Join(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 {
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
androidNDKPath = currentNDKPath
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
return true
}
}
return false
}
var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if runtime.GOOS == "windows" {
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
log.Fatal("missing gomobile installation")
}
} else {
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
log.Fatal("missing gomobile installation")
}
}
GoBinPath = goBin
}

View File

@ -0,0 +1,38 @@
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 ReadTagVersionRev() (badversion.Version, error) {
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
return badversion.Parse(currentTagRev[1:]), 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,218 @@
package main
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile() (string, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", fmt.Errorf("GOENV=off")
}
return file, nil
}
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
if dir == "" {
return "", fmt.Errorf("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
func GetRuntimeEnv(key string) (string, error) {
file, err := envFile()
if err != nil {
return "", err
}
if file == "" {
return "", fmt.Errorf("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
}
}
return runtimeEnv, nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN() string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
}
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func main() {
pwd, err := os.Getwd()
if err != nil {
fmt.Println("Can not get current working directory.")
os.Exit(1)
}
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := ""
if runtime.GOOS == "windows" {
suffix = ".exe"
}
protoc := "protoc"
if linkPath, err := os.Readlink(protoc); err == nil {
protoc = linkPath
}
protoFilesMap := make(map[string][]string)
walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err
}
if info.IsDir() {
return nil
}
dir := filepath.Dir(path)
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".proto") &&
filename != "typed_message.proto" &&
filename != "descriptor.proto" {
protoFilesMap[dir] = append(protoFilesMap[dir], path)
}
return nil
})
if walkErr != nil {
fmt.Println(walkErr)
os.Exit(1)
}
for _, files := range protoFilesMap {
for _, relProtoFile := range files {
args := []string{
"-I", ".",
"--go_out", pwd,
"--go_opt", "paths=source_relative",
"--go-grpc_out", pwd,
"--go-grpc_opt", "paths=source_relative",
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix),
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix),
}
args = append(args, relProtoFile)
cmd := exec.Command(protoc, args...)
cmd.Env = append(cmd.Env, os.Environ()...)
output, cmdErr := cmd.CombinedOutput()
if len(output) > 0 {
fmt.Println(string(output))
}
if cmdErr != nil {
fmt.Println(cmdErr)
os.Exit(1)
}
}
}
normalizeWalkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err
}
if info.IsDir() {
return nil
}
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".pb.go") &&
path != "config.pb.go" {
if err := NormalizeGeneratedProtoFile(path); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
})
if normalizeWalkErr != nil {
fmt.Println(normalizeWalkErr)
os.Exit(1)
}
}
func NormalizeGeneratedProtoFile(path string) error {
fd, err := os.OpenFile(path, os.O_RDWR, 0o644)
if err != nil {
return err
}
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
return err
}
out := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(fd)
valid := false
for scanner.Scan() {
if !valid && !strings.HasPrefix(scanner.Text(), "package ") {
continue
}
valid = true
out.Write(scanner.Bytes())
out.Write([]byte("\n"))
}
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
return err
}
err = fd.Truncate(0)
if err != nil {
return err
}
_, err = io.Copy(fd, bytes.NewReader(out.Bytes()))
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,71 @@
package main
import (
"flag"
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log"
)
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
var (
versionStr string
err error
)
if flagRunNightly {
var version badversion.Version
version, err = build_shared.ReadTagVersion()
if err == nil {
versionStr = version.String()
}
} else {
versionStr, err = build_shared.ReadTag()
}
if flagRunInCI {
if err != nil {
log.Fatal(err)
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(versionStr + "\n")
}
}
}
func setGitHubEnv(name string, value string) error {
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
_, err = outputFile.WriteString(name + "=" + value + "\n")
if err != nil {
outputFile.Close()
return err
}
err = outputFile.Close()
if err != nil {
return err
}
os.Stderr.WriteString(name + "=" + value + "\n")
return nil
}

View File

@ -0,0 +1,84 @@
package main
import (
"flag"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
)
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTag())
var androidPath string
if flagRunInCI {
androidPath = "clients/android"
} else {
androidPath = "../sing-box-for-android"
}
androidPath, err := filepath.Abs(androidPath)
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
var (
versionUpdated bool
goVersionUpdated bool
)
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion {
log.Info("updated version from ", propPair[1], " to ", newVersion)
versionUpdated = true
propPair[1] = newVersion
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
goVersionUpdated = true
propPair[1] = runtime.Version()
}
}
}
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
} else if flagRunInCI && !flagRunNightly {
log.Fatal("version changed, commit changes first.")
}
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])
}
}
var newProps []string
for _, propPair := range propsList {
newProps = append(newProps, strings.Join(propPair, "="))
}
common.Must(os.WriteFile("version.properties", []byte(strings.Join(newProps, "\n")), 0o644))
}

View File

@ -0,0 +1,145 @@
package main
import (
"flag"
"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"
)
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTagVersion())
var applePath string
if flagRunInCI {
applePath = "clients/apple"
} else {
applePath = "../sing-box-for-apple"
}
applePath, err := filepath.Abs(applePath)
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.sfavt"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.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

@ -0,0 +1,71 @@
package main
import (
"encoding/csv"
"io"
"net/http"
"os"
"strings"
"github.com/sagernet/sing-box/log"
"golang.org/x/exp/slices"
)
func main() {
err := updateMozillaIncludedRootCAs()
if err != nil {
log.Error(err)
}
}
func updateMozillaIncludedRootCAs() error {
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
if err != nil {
return err
}
defer response.Body.Close()
reader := csv.NewReader(response.Body)
header, err := reader.Read()
if err != nil {
return err
}
geoIndex := slices.Index(header, "Geographic Focus")
nameIndex := slices.Index(header, "Common Name or Certificate Name")
certIndex := slices.Index(header, "PEM Info")
generated := strings.Builder{}
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
package certificate
import "crypto/x509"
var mozillaIncluded *x509.CertPool
func init() {
mozillaIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return err
}
if record[geoIndex] == "China" {
continue
}
generated.WriteString("\n // ")
generated.WriteString(record[nameIndex])
generated.WriteString("\n")
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
cert := record[certIndex]
// Remove single quotes
cert = cert[1 : len(cert)-1]
generated.WriteString(cert)
generated.WriteString("`))\n")
}
generated.WriteString("}\n")
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
}

71
cmd/sing-box/cmd.go Normal file
View File

@ -0,0 +1,71 @@
package main
import (
"context"
"os"
"os/user"
"strconv"
"time"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
"github.com/spf13/cobra"
)
var (
globalCtx context.Context
configPaths []string
configDirectories []string
workingDir string
disableColor bool
)
var mainCommand = &cobra.Command{
Use: "sing-box",
PersistentPreRun: preRun,
}
func init() {
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")
}
func preRun(cmd *cobra.Command, args []string) {
globalCtx = context.Background()
sudoUser := os.Getenv("SUDO_USER")
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
sudoUserObject, _ := user.Lookup(sudoUser)
if sudoUserObject != nil {
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
}
}
if sudoUID > 0 && sudoGID > 0 {
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
}
if disableColor {
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
}
if workingDir != "" {
_, err := os.Stat(workingDir)
if err != nil {
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
}
err = os.Chdir(workingDir)
if err != nil {
log.Fatal(err)
}
}
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
}

View File

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

View File

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

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,36 @@
package main
import (
"os"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
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() {
commandGenerate.AddCommand(commandGenerateECHKeyPair)
}
func generateECHKeyPair(serverName string) error {
configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
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.GenerateCertificate(nil, nil, 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
}

43
cmd/sing-box/cmd_geoip.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var (
geoipReader *maxminddb.Reader
commandGeoIPFlagFile string
)
var commandGeoip = &cobra.Command{
Use: "geoip",
Short: "GeoIP tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geoipPreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, "file", "f", "geoip.db", "geoip file")
mainCommand.AddCommand(commandGeoip)
}
func geoipPreRun() error {
reader, err := maxminddb.Open(commandGeoIPFlagFile)
if err != nil {
return err
}
if reader.Metadata.DatabaseType != "sing-geoip" {
reader.Close()
return E.New("incorrect database type, expected sing-geoip, got ", reader.Metadata.DatabaseType)
}
geoipReader = reader
return nil
}

View File

@ -0,0 +1,98 @@
package main
import (
"io"
"net"
"os"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var flagGeoipExportOutput string
const flagGeoipExportDefaultOutput = "geoip-<country>.srs"
var commandGeoipExport = &cobra.Command{
Use: "export <country>",
Short: "Export geoip country as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoipExport.Flags().StringVarP(&flagGeoipExportOutput, "output", "o", flagGeoipExportDefaultOutput, "Output path")
commandGeoip.AddCommand(commandGeoipExport)
}
func geoipExport(countryCode string) error {
networks := geoipReader.Networks(maxminddb.SkipAliasedNetworks)
countryMap := make(map[string][]*net.IPNet)
var (
ipNet *net.IPNet
nextCountryCode string
err error
)
for networks.Next() {
ipNet, err = networks.Network(&nextCountryCode)
if err != nil {
return err
}
countryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)
}
ipNets := countryMap[strings.ToLower(countryCode)]
if len(ipNets) == 0 {
return E.New("country code not found: ", countryCode)
}
var (
outputFile *os.File
outputWriter io.Writer
)
if flagGeoipExportOutput == "stdout" {
outputWriter = os.Stdout
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
headlessRule.IPCIDR = make([]string, 0, len(ipNets))
for _, cidr := range ipNets {
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
}
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion2
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@ -0,0 +1,31 @@
package main
import (
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGeoipList = &cobra.Command{
Use: "list",
Short: "List geoip country codes",
Run: func(cmd *cobra.Command, args []string) {
err := listGeoip()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipList)
}
func listGeoip() error {
for _, code := range geoipReader.Metadata.Languages {
os.Stdout.WriteString(code + "\n")
}
return nil
}

View File

@ -0,0 +1,47 @@
package main
import (
"net/netip"
"os"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra"
)
var commandGeoipLookup = &cobra.Command{
Use: "lookup <address>",
Short: "Lookup if an IP address is contained in the GeoIP database",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipLookup(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipLookup)
}
func geoipLookup(address string) error {
addr, err := netip.ParseAddr(address)
if err != nil {
return E.Cause(err, "parse address")
}
if !N.IsPublicAddr(addr) {
os.Stdout.WriteString("private\n")
return nil
}
var code string
_ = geoipReader.Lookup(addr.AsSlice(), &code)
if code != "" {
os.Stdout.WriteString(code + "\n")
return nil
}
os.Stdout.WriteString("unknown\n")
return nil
}

View File

@ -0,0 +1,41 @@
package main
import (
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var (
commandGeoSiteFlagFile string
geositeReader *geosite.Reader
geositeCodeList []string
)
var commandGeoSite = &cobra.Command{
Use: "geosite",
Short: "Geosite tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geositePreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file")
mainCommand.AddCommand(commandGeoSite)
}
func geositePreRun() error {
reader, codeList, err := geosite.Open(commandGeoSiteFlagFile)
if err != nil {
return E.Cause(err, "open geosite file")
}
geositeReader = reader
geositeCodeList = codeList
return nil
}

View File

@ -0,0 +1,81 @@
package main
import (
"io"
"os"
"github.com/sagernet/sing-box/common/geosite"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var commandGeositeExportOutput string
const commandGeositeExportDefaultOutput = "geosite-<category>.json"
var commandGeositeExport = &cobra.Command{
Use: "export <category>",
Short: "Export geosite category as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geositeExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, "output", "o", commandGeositeExportDefaultOutput, "Output path")
commandGeoSite.AddCommand(commandGeositeExport)
}
func geositeExport(category string) error {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
var (
outputFile *os.File
outputWriter io.Writer
)
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
defaultRule := geosite.Compile(sourceSet)
headlessRule.Domain = defaultRule.Domain
headlessRule.DomainSuffix = defaultRule.DomainSuffix
headlessRule.DomainKeyword = defaultRule.DomainKeyword
headlessRule.DomainRegex = defaultRule.DomainRegex
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion2
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@ -0,0 +1,50 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
var commandGeositeList = &cobra.Command{
Use: "list <category>",
Short: "List geosite categories",
Run: func(cmd *cobra.Command, args []string) {
err := geositeList()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeList)
}
func geositeList() error {
var geositeEntry []struct {
category string
items int
}
for _, category := range geositeCodeList {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
geositeEntry = append(geositeEntry, struct {
category string
items int
}{category, len(sourceSet)})
}
sort.SliceStable(geositeEntry, func(i, j int) bool {
return geositeEntry[i].items < geositeEntry[j].items
})
for _, entry := range geositeEntry {
os.Stdout.WriteString(F.ToString(entry.category, " (", entry.items, ")\n"))
}
return nil
}

View File

@ -0,0 +1,97 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var commandGeositeLookup = &cobra.Command{
Use: "lookup [category] <domain>",
Short: "Check if a domain is in the geosite",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
var (
source string
target string
)
switch len(args) {
case 1:
target = args[0]
case 2:
source = args[0]
target = args[1]
}
err := geositeLookup(source, target)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeLookup)
}
func geositeLookup(source string, target string) error {
var sourceMatcherList []struct {
code string
matcher *searchGeositeMatcher
}
if source != "" {
sourceSet, err := geositeReader.Read(source)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+source)
}
sourceMatcherList = []struct {
code string
matcher *searchGeositeMatcher
}{
{
code: source,
matcher: sourceMatcher,
},
}
} else {
for _, code := range geositeCodeList {
sourceSet, err := geositeReader.Read(code)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+code)
}
sourceMatcherList = append(sourceMatcherList, struct {
code string
matcher *searchGeositeMatcher
}{
code: code,
matcher: sourceMatcher,
})
}
}
sort.SliceStable(sourceMatcherList, func(i, j int) bool {
return sourceMatcherList[i].code < sourceMatcherList[j].code
})
for _, matcherItem := range sourceMatcherList {
if matchRule := matcherItem.matcher.Match(target); matchRule != "" {
os.Stdout.WriteString("Match code (")
os.Stdout.WriteString(matcherItem.code)
os.Stdout.WriteString(") ")
os.Stdout.WriteString(matchRule)
os.Stdout.WriteString("\n")
}
}
return nil
}

View File

@ -0,0 +1,56 @@
package main
import (
"regexp"
"strings"
"github.com/sagernet/sing-box/common/geosite"
)
type searchGeositeMatcher struct {
domainMap map[string]bool
suffixList []string
keywordList []string
regexList []string
}
func newSearchGeositeMatcher(items []geosite.Item) (*searchGeositeMatcher, error) {
options := geosite.Compile(items)
domainMap := make(map[string]bool)
for _, domain := range options.Domain {
domainMap[domain] = true
}
rule := &searchGeositeMatcher{
domainMap: domainMap,
suffixList: options.DomainSuffix,
keywordList: options.DomainKeyword,
regexList: options.DomainRegex,
}
return rule, nil
}
func (r *searchGeositeMatcher) Match(domain string) string {
if r.domainMap[domain] {
return "domain=" + domain
}
for _, suffix := range r.suffixList {
if strings.HasSuffix(domain, suffix) {
return "domain_suffix=" + suffix
}
}
for _, keyword := range r.keywordList {
if strings.Contains(domain, keyword) {
return "domain_keyword=" + keyword
}
}
for _, regexStr := range r.regexList {
regex, err := regexp.Compile(regexStr)
if err != nil {
continue
}
if regex.MatchString(domain) {
return "domain_regex=" + regexStr
}
}
return ""
}

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

@ -0,0 +1,143 @@
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
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/json"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var commandMerge = &cobra.Command{
Use: "merge <output-path>",
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.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}
func mergePathResources(options *option.Options) error {
for _, inbound := range options.Inbounds {
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
}
}
for _, outbound := range options.Outbounds {
switch outbound.Type {
case C.TypeSSH:
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
}
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
}
}
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) {
if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
func trimStringArray(array []string) []string {
return common.Filter(array, func(it string) bool {
return strings.TrimSpace(it) != ""
})
}

View File

@ -0,0 +1,14 @@
package main
import (
"github.com/spf13/cobra"
)
var commandRuleSet = &cobra.Command{
Use: "rule-set",
Short: "Manage rule-sets",
}
func init() {
mainCommand.AddCommand(commandRuleSet)
}

View File

@ -0,0 +1,80 @@
package main
import (
"io"
"os"
"strings"
"github.com/sagernet/sing-box/common/srs"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetCompileOutput string
const flagRuleSetCompileDefaultOutput = "<file_name>.srs"
var commandRuleSetCompile = &cobra.Command{
Use: "compile [source-path]",
Short: "Compile rule-set json to binary",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := compileRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSet.AddCommand(commandRuleSetCompile)
commandRuleSetCompile.Flags().StringVarP(&flagRuleSetCompileOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file")
}
func compileRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
plainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
var outputPath string
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".json") {
outputPath = sourcePath[:len(sourcePath)-5] + ".srs"
} else {
outputPath = sourcePath + ".srs"
}
} else {
outputPath = flagRuleSetCompileOutput
}
outputFile, err := os.Create(outputPath)
if err != nil {
return err
}
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
if err != nil {
outputFile.Close()
os.Remove(outputPath)
return err
}
outputFile.Close()
return nil
}

View File

@ -0,0 +1,89 @@
package main
import (
"io"
"os"
"strings"
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var (
flagRuleSetConvertType string
flagRuleSetConvertOutput string
)
var commandRuleSetConvert = &cobra.Command{
Use: "convert [source-path]",
Short: "Convert adguard DNS filter to rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := convertRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSet.AddCommand(commandRuleSetConvert)
commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertType, "type", "t", "", "Source type, available: adguard")
commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file")
}
func convertRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
var rules []option.HeadlessRule
switch flagRuleSetConvertType {
case "adguard":
rules, err = adguard.Convert(reader)
case "":
return E.New("source type is required")
default:
return E.New("unsupported source type: ", flagRuleSetConvertType)
}
if err != nil {
return err
}
var outputPath string
if flagRuleSetConvertOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".txt") {
outputPath = sourcePath[:len(sourcePath)-4] + ".srs"
} else {
outputPath = sourcePath + ".srs"
}
} else {
outputPath = flagRuleSetConvertOutput
}
outputFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outputFile.Close()
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
if err != nil {
outputFile.Close()
os.Remove(outputPath)
return err
}
outputFile.Close()
return nil
}

View File

@ -0,0 +1,77 @@
package main
import (
"io"
"os"
"strings"
"github.com/sagernet/sing-box/common/srs"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetDecompileOutput string
const flagRuleSetDecompileDefaultOutput = "<file_name>.json"
var commandRuleSetDecompile = &cobra.Command{
Use: "decompile [binary-path]",
Short: "Decompile rule-set binary to json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := decompileRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSet.AddCommand(commandRuleSetDecompile)
commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file")
}
func decompileRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
ruleSet, err := srs.Read(reader, true)
if err != nil {
return err
}
var outputPath string
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") {
outputPath = sourcePath[:len(sourcePath)-4] + ".json"
} else {
outputPath = sourcePath + ".json"
}
} else {
outputPath = flagRuleSetDecompileOutput
}
outputFile, err := os.Create(outputPath)
if err != nil {
return err
}
encoder := json.NewEncoder(outputFile)
encoder.SetIndent("", " ")
err = encoder.Encode(ruleSet)
if err != nil {
outputFile.Close()
os.Remove(outputPath)
return err
}
outputFile.Close()
return nil
}

View File

@ -0,0 +1,83 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var commandRuleSetFormatFlagWrite bool
var commandRuleSetFormat = &cobra.Command{
Use: "format <source-path>",
Short: "Format rule-set json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := formatRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetFormat.Flags().BoolVarP(&commandRuleSetFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
commandRuleSet.AddCommand(commandRuleSetFormat)
}
func formatRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
plainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSet)
if err != nil {
return E.Cause(err, "encode config")
}
outputPath, _ := filepath.Abs(sourcePath)
if !commandRuleSetFormatFlagWrite || sourcePath == "stdin" {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(content, buffer.Bytes()) {
return nil
}
output, err := os.Create(sourcePath)
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
}

View File

@ -0,0 +1,105 @@
package main
import (
"bytes"
"context"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
M "github.com/sagernet/sing/common/metadata"
"github.com/spf13/cobra"
)
var flagRuleSetMatchFormat string
var commandRuleSetMatch = &cobra.Command{
Use: "match <rule-set path> <IP address/domain>",
Short: "Check if an IP address or a domain matches the rule-set",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := ruleSetMatch(args[0], args[1])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, "format", "f", "source", "rule-set format")
commandRuleSet.AddCommand(commandRuleSetMatch)
}
func ruleSetMatch(sourcePath string, domain string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return E.Cause(err, "read rule-set")
}
}
content, err := io.ReadAll(reader)
if err != nil {
return E.Cause(err, "read rule-set")
}
if flagRuleSetMatchFormat == "" {
switch filepath.Ext(sourcePath) {
case ".json":
flagRuleSetMatchFormat = C.RuleSetFormatSource
case ".srs":
flagRuleSetMatchFormat = C.RuleSetFormatBinary
}
}
var ruleSet option.PlainRuleSetCompat
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
case C.RuleSetFormatBinary:
ruleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
}
plainRuleSet, err := ruleSet.Upgrade()
if err != nil {
return err
}
ipAddress := M.ParseAddr(domain)
var metadata adapter.InboundContext
if ipAddress.IsValid() {
metadata.Destination = M.SocksaddrFrom(ipAddress, 0)
} else {
metadata.Domain = domain
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
if currentRule.Match(&metadata) {
println(F.ToString("match rules.[", i, "]: ", currentRule))
}
}
return nil
}

View File

@ -0,0 +1,162 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var (
ruleSetPaths []string
ruleSetDirectories []string
)
var commandRuleSetMerge = &cobra.Command{
Use: "merge <output-path>",
Short: "Merge rule-set source files",
Run: func(cmd *cobra.Command, args []string) {
err := mergeRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
commandRuleSet.AddCommand(commandRuleSetMerge)
}
type RuleSetEntry struct {
content []byte
path string
options option.PlainRuleSetCompat
}
func readRuleSetAt(path string) (*RuleSetEntry, error) {
var (
configContent []byte
err error
)
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(path)
}
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
return &RuleSetEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readRuleSet() ([]*RuleSetEntry, error) {
var optionsList []*RuleSetEntry
for _, path := range ruleSetPaths {
optionsEntry, err := readRuleSetAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range ruleSetDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read rule-set directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readRuleSetAt(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 readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
optionsList, err := readRuleSet()
if err != nil {
return option.PlainRuleSetCompat{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var optionVersion uint8
for _, options := range optionsList {
if optionVersion < options.options.Version {
optionVersion = options.options.Version
}
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
}
}
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
}
mergedOptions.Version = optionVersion
return mergedOptions, nil
}
func mergeRuleSet(outputPath string) error {
mergedOptions, err := readRuleSetAndMerge()
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.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@ -0,0 +1,95 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var commandRuleSetUpgradeFlagWrite bool
var commandRuleSetUpgrade = &cobra.Command{
Use: "upgrade <source-path>",
Short: "Upgrade rule-set json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := upgradeRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetUpgrade.Flags().BoolVarP(&commandRuleSetUpgradeFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
commandRuleSet.AddCommand(commandRuleSetUpgrade)
}
func upgradeRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
plainRuleSetCompat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
switch plainRuleSetCompat.Version {
case C.RuleSetVersion1:
default:
log.Info("already up-to-date")
return nil
}
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
if err != nil {
return err
}
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSetCompat)
if err != nil {
return E.Cause(err, "encode config")
}
outputPath, _ := filepath.Abs(sourcePath)
if !commandRuleSetUpgradeFlagWrite || sourcePath == "stdin" {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(content, buffer.Bytes()) {
return nil
}
output, err := os.Create(sourcePath)
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
}

View File

@ -2,17 +2,23 @@ package main
import ( import (
"context" "context"
"net/http" "io"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
runtimeDebug "runtime/debug"
"sort"
"strings"
"syscall" "syscall"
"time"
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
"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/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,25 +26,106 @@ import (
var commandRun = &cobra.Command{ var commandRun = &cobra.Command{
Use: "run", Use: "run",
Short: "Run service", Short: "Run service",
Run: run, Run: func(cmd *cobra.Command, args []string) {
} err := run()
func run(cmd *cobra.Command, args []string) {
err := run0()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
},
} }
func run0() error { func init() {
configContent, err := os.ReadFile(configPath) mainCommand.AddCommand(commandRun)
if err != nil { }
return E.Cause(err, "read config")
type OptionsEntry struct {
content []byte
path string
options option.Options
}
func readConfigAt(path string) (*OptionsEntry, error) {
var (
configContent []byte
err error
)
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(path)
} }
var options option.Options
err = json.Unmarshal(configContent, &options)
if err != nil { if err != nil {
return E.Cause(err, "decode config") return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
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 mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}
}
var mergedOptions option.Options
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config")
}
return mergedOptions, nil
}
func create() (*box.Box, context.CancelFunc, error) {
options, err := readConfigAndMerge()
if err != nil {
return nil, nil, err
} }
if disableColor { if disableColor {
if options.Log == nil { if options.Log == nil {
@ -46,27 +133,80 @@ func run0() error {
} }
options.Log.DisableColor = true options.Log.DisableColor = true
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(globalCtx)
instance, err := box.New(ctx, options) instance, err := box.New(box.Options{
if err != nil { Context: ctx,
cancel() Options: options,
return E.Cause(err, "create service")
}
err = instance.Start()
if err != nil {
cancel()
return E.Cause(err, "start service")
}
if debug.Enabled {
http.HandleFunc("/debug/close", func(writer http.ResponseWriter, request *http.Request) {
cancel()
instance.Close()
}) })
} if err != nil {
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
<-osSignals
cancel() cancel()
instance.Close() 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")
}
return instance, cancel, nil
}
func run() error {
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(osSignals)
for {
instance, cancel, err := create()
if err != nil {
return err
}
runtimeDebug.FreeOSMemory()
for {
osSignal := <-osSignals
if osSignal == syscall.SIGHUP {
err = check()
if err != nil {
log.Error(E.Cause(err, "reload service"))
continue
}
}
cancel()
closeCtx, closed := context.WithCancel(context.Background())
go closeMonitor(closeCtx)
err = instance.Close()
closed()
if osSignal != syscall.SIGHUP {
if err != nil {
log.Error(E.Cause(err, "sing-box did not closed properly"))
}
return nil return nil
} }
break
}
}
}
func closeMonitor(ctx context.Context) {
time.Sleep(C.FatalStopTimeout)
select {
case <-ctx.Done():
return
default:
}
log.Fatal("sing-box did not close!")
}

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