Compare commits

..

143 Commits

Author SHA1 Message Date
世界
1c36072120 documentation: Bump version 2025-05-12 18:12:57 +08:00
世界
6e9258bede Add missing accept_routes option for Tailscale 2025-05-12 18:12:57 +08:00
世界
b0f4008071 Add TLS record fragment support 2025-05-12 17:43:54 +08:00
世界
3f26b7b4e9 release: Update Go to 1.24.3 2025-05-12 17:43:54 +08:00
世界
dbab3a2178 Fix set edns0 client subnet 2025-05-12 17:43:53 +08:00
世界
dbf17cca71 Update minor dependencies 2025-05-12 17:43:53 +08:00
世界
4a0fe88217 Update certmagic and providers 2025-05-12 17:43:53 +08:00
世界
4a62401f9e Update protobuf and grpc 2025-05-12 17:43:52 +08:00
世界
09edc643cb Add control options for listeners 2025-05-12 17:43:52 +08:00
世界
4be4220f20 Update quic-go to v0.51.0 2025-05-12 17:43:52 +08:00
世界
d1b0c967aa Update utls to v1.7.0 2025-05-12 17:43:52 +08:00
世界
7608266ea7 Handle EDNS version downgrade 2025-05-12 17:43:22 +08:00
世界
081dad8f57 documentation: Fix anytls padding scheme description 2025-05-12 17:43:21 +08:00
安容
3ff990a6a3 Report invalid DNS address early 2025-05-12 17:43:21 +08:00
世界
9ff3da2bdb Fix wireguard listen_port 2025-05-12 17:43:20 +08:00
世界
92c20a545d clash-api: Add more meta api 2025-05-12 17:43:20 +08:00
世界
2e75cb3c22 Fix DNS lookup 2025-05-12 17:43:20 +08:00
世界
4414dc6bee Fix fetch ECH configs 2025-05-12 17:43:19 +08:00
reletor
7d11245a90 documentation: Minor fixes 2025-05-12 17:43:19 +08:00
caelansar
744e9b4577 Fix callback deletion in UDP transport 2025-05-12 17:43:18 +08:00
世界
153a262f35 documentation: Try to make the play review happy 2025-05-12 17:43:18 +08:00
世界
39d2fb5467 Fix missing handling of legacy domain_strategy options 2025-05-12 17:43:18 +08:00
世界
85a2a5c169 Improve local DNS server 2025-05-12 17:43:18 +08:00
anytls
575b5dfb10 Update anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:17 +08:00
世界
f19c500b4f Fix DNS dialer 2025-05-12 17:43:17 +08:00
世界
8e5958a7e4 release: Skip override version for iOS 2025-05-12 17:43:17 +08:00
iikira
dc5e6ed488 Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-05-12 17:43:16 +08:00
ReleTor
049c18377c Fix fetch ECH configs 2025-05-12 17:43:16 +08:00
世界
870f76a91e Allow direct outbounds without domain_resolver 2025-05-12 17:43:15 +08:00
世界
d1a945f180 Fix Tailscale dialer 2025-05-12 17:43:15 +08:00
dyhkwong
6d56bc1528 Fix DNS over QUIC stream close 2025-05-12 17:43:14 +08:00
anytls
25e2d670e7 Update anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:14 +08:00
Rambling2076
be8419fa84 Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-05-12 17:43:14 +08:00
世界
27acc99c35 Fail when default DNS server not found 2025-05-12 17:43:14 +08:00
世界
6053a3bad1 Update gVisor to 20250319.0 2025-05-12 17:43:14 +08:00
世界
c63a58c0fb Explicitly reject detour to empty direct outbounds 2025-05-12 17:43:13 +08:00
世界
cc4354b18a Add netns support 2025-05-12 17:43:13 +08:00
世界
8344653443 Add wildcard name support for predefined records 2025-05-12 17:43:13 +08:00
世界
26f185aa7c Remove map usage in options 2025-05-12 17:43:13 +08:00
世界
a69007f1a2 Fix unhandled DNS loop 2025-05-12 17:43:13 +08:00
世界
20729defd6 Add wildcard-sni support for shadow-tls inbound 2025-05-12 17:43:12 +08:00
k9982874
ff751f8504 Add ntp protocol sniffing 2025-05-12 17:43:11 +08:00
世界
689c229fe6 option: Fix marshal legacy DNS options 2025-05-12 17:43:11 +08:00
世界
c4cdb68d10 Make domain_resolver optional when only one DNS server is configured 2025-05-12 17:43:11 +08:00
世界
dd99a45df6 Fix DNS lookup context pollution 2025-05-12 17:43:11 +08:00
世界
cbe077eaa9 Fix http3 DNS server connecting to wrong address 2025-05-12 17:43:10 +08:00
Restia-Ashbell
47fd88f450 documentation: Fix typo 2025-05-12 17:43:10 +08:00
anytls
5fdfba6816 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:10 +08:00
k9982874
e38de3bd8f Fix hosts DNS server 2025-05-12 17:43:10 +08:00
世界
6f9d507ab3 Fix UDP DNS server crash 2025-05-12 17:43:09 +08:00
世界
567de6aa88 documentation: Fix missing ip_accept_any DNS rule option 2025-05-12 17:43:09 +08:00
世界
5fd7fba75f Fix anytls dialer usage 2025-05-12 17:43:09 +08:00
世界
d80f81157c Move predefined DNS server to rule action 2025-05-12 17:43:09 +08:00
世界
133e57e85f Fix domain resolver on direct outbound 2025-05-12 17:43:08 +08:00
Zephyruso
f854586ac8 Fix missing AnyTLS display name 2025-05-12 17:43:08 +08:00
anytls
a3a54b6e91 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:08 +08:00
Estel
fa5dbfcca8 documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-05-12 17:43:07 +08:00
TargetLocked
35d5a1f6a6 Fix parsing legacy DNS options 2025-05-12 17:43:07 +08:00
世界
9f26fea1fc Fix DNS fallback 2025-05-12 17:43:07 +08:00
世界
5c71baa23c documentation: Fix missing hosts DNS server 2025-05-12 17:43:06 +08:00
anytls
a75772d01e Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-05-12 17:43:06 +08:00
ReleTor
ae263e2da3 documentation: Minor fixes 2025-05-12 17:43:05 +08:00
libtry486
031b6de8b5 documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-05-12 17:43:05 +08:00
Alireza Ahmadi
6aef4ec1c6 Fix Outbound deadlock 2025-05-12 17:43:05 +08:00
世界
61b7666fd5 documentation: Fix AnyTLS doc 2025-05-12 17:43:04 +08:00
anytls
ea5339285e Add AnyTLS protocol 2025-05-12 17:43:04 +08:00
世界
d38d58ff6e Migrate to stdlib ECH support 2025-05-12 17:43:03 +08:00
世界
60570b966b Add fallback local DNS server for iOS 2025-05-12 17:43:03 +08:00
世界
195e859bf7 Get darwin local DNS server from libresolv 2025-05-12 17:43:02 +08:00
世界
0ce1f21794 Improve resolve action 2025-05-12 17:43:02 +08:00
世界
ec5eba1d25 Fix toolchain version 2025-05-12 17:43:02 +08:00
世界
682a95c55e Add back port hopping to hysteria 1 2025-05-12 17:43:01 +08:00
xchacha20-poly1305
34283f914a Remove single quotes of raw Moziila certs 2025-05-12 17:43:01 +08:00
世界
fcce280a50 Add Tailscale endpoint 2025-05-12 17:42:41 +08:00
世界
16e541f50e Build legacy binaries with latest Go 2025-05-07 15:13:00 +08:00
世界
a22849171a documentation: Remove outdated icons 2025-05-07 15:13:00 +08:00
世界
0597a0a8d7 documentation: Certificate store 2025-05-07 15:12:59 +08:00
世界
791d9581bc documentation: TLS fragment 2025-05-07 15:12:59 +08:00
世界
b4d9e7417a documentation: Outbound domain resolver 2025-05-07 15:12:59 +08:00
世界
e429db2ca5 documentation: Refactor DNS 2025-05-07 15:12:59 +08:00
世界
a1b717ecea Add certificate store 2025-05-07 15:12:59 +08:00
世界
c37b74730d Add TLS fragment support 2025-05-07 15:12:59 +08:00
世界
6bd0a372c1 refactor: Outbound domain resolver 2025-05-07 15:12:58 +08:00
世界
6752ade2a0 refactor: DNS 2025-05-07 15:12:50 +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
224 changed files with 3028 additions and 8819 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

21
.fpm_systemd Normal file
View File

@@ -0,0 +1,21 @@
-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
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/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

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

View File

@@ -1,3 +0,0 @@
# goreleaser
I'm sorry for this, but I can't afford to renew for now because the subscription is too expensive for an open source project.

Binary file not shown.

View File

@@ -1,87 +0,0 @@
{
"dns": {
"servers": [
{
"type": "tls",
"server": "8.8.8.8"
}
]
},
"inbounds": [
{
"type": "tun",
"address": [
"172.19.0.1/30",
"fdfe:dcba:9876::1/126"
],
"auto_route": true,
"auto_redirect": true
}
],
"certificate": {
"tls_decryption": {
"enabled": true,
"key_pair_p12": "MIIKYQIBAzCCChEGCSqGSIb3DQEHAaCCCgIEggn+MIIJ+jCCBGgGCSqGSIb3DQEHBqCCBFkwggRVAgEAMIIETgYJKoZIhvcNAQcBMF0GCSqGSIb3DQEFDTBQMC8GCSqGSIb3DQEFDDAiBBBxLjkB6wrMHpRNPnq8KUnXAgIIADAKBggqhkiG9w0CCTAdBglghkgBZQMEASoEEHFou8IR0ZPb9O4NaLDC5LKAggPgL/7EoJRMEx5ZDVm2ZUQRuGyjS+lMB4JDZiykYfvfzMtQ2LZ+aO90rLxYFh4uBpbu+mmA0WDF/HU3GbE0nyY9beo0RAh0/u2Ak2kkfDSntRPVTl5zNBrT9hEtH9oSlN7tok9SMhWEJlsoIRhGinJwsDnDbXcIqkIj/oqtXlSJc6gA7CYf6AJRrVjP1Wtk80GMrMfYNvQw9bich5fs4biddf0xtR13YFV80rCPb+HtTT4KYa7Rzo5qR/cNHsMP/3v5BT2UszpaSIokPoW8ta1RWcQNXuH3OHjG4GMjg88w6xtyudIKrTyP0BTRfIJ2S2EtsWGHU2Gmr/MUY0a7abbtG+LVdSCRTgDoNeiY4C7lkQEOpefoZHWa3+jeGu17812YZHxfCZuhFy33rZgqngWRN1cdxoAbhozChtKmn0Uhdox7jqUw5M/Sj4DWHm0RNB8Ffvf39i/zvlfORzljIiwAKiB26FwpcKKRfx7rrjx4xRLkTLWl0DnJKxOcVz/oXSjglpHJvUSMgbpzXEHHQ7+d+K/WTnoj+dONifxiWBt1hQA8qoPiQceYWGY37oeWvGZI/Qv3ZSO5Mm/yVAuAFyOzJdpW5aC3Kq3gwNVbKNeeV5fWDtvP2K9XcgZFv8OqpNnvLmaL+iWHTPg5wYGvf0iWPr8NVU6OQpSZCOTodwOGfcpQ2YlCnkBgkjkJFLNuM4mi1U9kyTZWAYyZ6zVort0eezJcBoQGBBV2/GkFmwDNa9Q8mT8S7QTf7ZqAtyMnM9rBch7zIscBk6swG/KhgFRtUmDLpY6tpMb6vHHueu4duaUvIXvdjgTe4oE2Ou36VZ1+dC+RswmGCMwFlHqsZiIfU26SDiC3G9wH0iIg6th3LrDJYYD57l5Ps0pVjS7RAYYzu1lA2d2wGEFBJ3UEpJp257Wv2I6foeoTYXSX/XM1JUuFv1516qSqwPk4a1E6N6J+d+iWvM7BBcwakMG1XSUT4zhHrBzPPxXCCBeJHTcOoiaqXwaqsBBButSxViysvGZcBbyAxZNtmXCDh33a760XF4tb1f0mb2jW13CMARGOeubM3Z21eoc16tFkoKSD3wlzT2VlxVuUIgBT+wx4GOWgldngn2aXWInOkaEFdwABBLh5egxNBAI2tzirk6ijpRCq+gquTbEhxIwJavCfdYc0lqMevsEiZxqjoZHEf1EoId/rd3TEdclRf5OzLjbSbDICtFI82S5A+wDMXltVmB+Rw1mBZZUhvbUUOC7ARQ0pkE8DfwgPviFo2z8/i++3Mb02D57V1Mz1k6PB56QzlEOTJrmaBXSQs7U8Aiuln6CA+McwggWKBgkqhkiG9w0BBwGgggV7BIIFdzCCBXMwggVvBgsqhkiG9w0BDAoBAqCCBTcwggUzMF0GCSqGSIb3DQEFDTBQMC8GCSqGSIb3DQEFDDAiBBCl+G6epsuiNjP2afUFOwazAgIIADAKBggqhkiG9w0CCTAdBglghkgBZQMEASoEEG2FLRo+Ud+dbzCVbrer71YEggTQ81fiT0+gLnYWZpNq0MV/kPma4P+sws4wRd5CVG5rCMwmmr3JUCVk66uYLZTBXqHJ0qy3CPE2K1siImQJNS3DMD1q9WVCLPFEPLbO1ycsV73AOMc2UNJMkY7AgGCMpK+u/afMewsnAk/fmwjTw5qOm21TeesahwVvIMb3pQrkFu8FSIWK9IPRX7VCiYSa/KajiFKi0/lWEk9/LJEfikqGOB3FWYQkrV4jhhh+SNMm5LATgNgZ3FyhleruJZup0PN25W2IrpjcEBr9gHVU6gsCyB4PTTrVfopLq7goDWnOQeeAa4Y98QN6nT0EyqkfKU678/JeLz0gW8zijgdqzLwwucLg6cGE379d/2igE7/SJO8qa/JAjD3RDe88N97ysKW7vOOvIH6DnmkgQc8Cq/KKOyVlrDNx65YEft1oqVE3L5IfnmHT5ycbzyMJpdB6uL6OT9KqVLB2bHWDH47XfI8I8z56mzmKSXrWGm93beYV8u908Rokj82LHGEf9th6ttBZykWZgS+hQjc3jIU8xpa2/7mpPVFBCTiphBtp3+fCEVKmnubiiwe28Lw+xEvX8oAEGXhi5fNIGrAXvMk/rgpoh44wQwET6WnyiO8Ad8hOxvPtwgGD0m0FNFlv+yIGzY1PZeevquLKEwtvllo/A3g0OUbeGC2qC5s8VGkv11FRQPdUnOV2oXvosAWqxh6SnVrG8xbxc5L2xjJuUH8b70ne4iXzcfXo5FubtLuuJ6WNFWO9UasmvKaMqFZDlMK8FMcNTq6X0m8ilRZf056C3FDQAMxIa9mKyWebm3+4+LfxjgWo1dxvXR5HnMpzCbcoz/TIbSiUzSTaihxpzMi7Cvkc/JqPTTSkqjR+jLw7tOZucP8VtpQmQvqg2fd1hBgqam37qVC45D2765/V5v74+gtn5nc6HrGOEwpLlqcy5kojrjhQkNUkS7x5vg1KOFP/9uoC67qRFaGH5EM0XAdTVAyt1gn+StXVCXsNKvX93BPaNwL7we/zYZPpERFHaVD9R1Fw2Bz0+RzcdNQqP9yiq3mmmGNZHS0KSAKP3cmA3pwt9gPjpt/L1VNFgkVti2/YIDF37c3yuU9ZBI6kA7LhkcH5j0APr1ppS+Zxw6UKhsZDSGySqPyz3C2k4wy+R2+8mO1dN9haRW0smWWnziHWh8OFGhG+ghvc2HiX1tg2dTrByIFr9wixs4Kn9wDg0Qc1mS0+2+KacO8todl3jVYsLhcSTt5d2b/ZHuyAx4UPFtWPPF1vFdRMnf1jq83q/OEcTSfqkiEpEzs6NXpDEy1E0neq+LVHXi37IHzTGjjIvBnE2KZUoUdiFYitfDoUQpdhSpWKZTsmpqVXi/b7TZ20scvt5Qb6nfEWNds7hyGhnzAGQIV64xaDhKDB1p3QpDYxsJHvAGC1Yj9CY5w+sYpOjsfUo0qKeaFmu0fWX44s388GjZbid92/UvIxN9Lt/jri2xq+XPjJR194hc2ITUDrZvaaqeZ4odH4HXUC7FMqL6NVeX6MIv4g2QQkrt9DO35LokztOQCeuaA4rOZiM7mR3JJZIXf2jFNwElU0bvUJY2eYcQwTSQPXBzMz0AvhXxJiOFx3IQHHl55j4KMpab/NNbHChDUWJ7ptLX0/x8R3scJjCqwxJTAjBgkqhkiG9w0BCRUxFgQU6W6dFe0wnwEJqyaK7H5cUfUvxzQwRzAvMAsGCWCGSAFlAwQCAQQghQoDpo4gS46c+xoCeAykL69ZRT3zYrgNkgvL6s2UIVsEEHkNJl2cMvSFmZ8gi+cS/vQCAggA",
"key_pair_p12_password": "D173A3D9"
}
},
"mitm": {
"enabled": true,
"http2_enabled": true
},
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"type": "logical",
"mode": "or",
"rules": [
{
"network": "udp",
"port": 53
},
{
"protocol": "dns"
}
],
"action": "hijack-dns"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"action": "resolve"
},
{
"domain": "goreleaser.com",
"action": "route-options",
"mitm": {
"enabled": true,
"surge_map_local": [
"^https://goreleaser\\.com/static/latest-pro data-type=text data=\"(update check disabled)\""
]
}
},
{
"domain": "api.gumroad.com",
"action": "route-options",
"mitm": {
"enabled": true,
"surge_map_local": [
"^https://api\\.gumroad\\.com/v2/licenses/verify data-type=file data=.github/goreleaser/response.json header=\"Content-Type:application/json\""
]
}
}
],
"auto_detect_interface": true
}
}

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
release/local/install_minimal.sh
sudo cp .github/goreleaser/config.json /usr/local/etc/sing-box/config.json
sudo mkdir -p /var/lib/sing-box/.github/goreleaser
sudo cp .github/goreleaser/response.json /var/lib/sing-box/.github/goreleaser/response.json
go run -v ./cmd/sing-box tools install-ca .github/goreleaser/ca.crt
sudo systemctl start sing-box
sleep 5

View File

@@ -1,12 +0,0 @@
{
"success": true,
"purchase": {
"license_key": "fake-key",
"subscription_id": "fake-id",
"product_id": "7ev6hHL7RZc753daE5bRNw==",
"product_permalink": "https:\/\/beckersoft.gumroad.com\/l\/goreleaser",
"seller_id": "A2wDalJj66fJdFU_jwy_oA==",
"short_product_id": "CadfZ",
"permalink": "goreleaser"
}
}

View File

@@ -1,10 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="1.23.6" VERSION="1.23.6"
mkdir -p $HOME/go
cd $HOME/go
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz" wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz" tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go $HOME/go/go_legacy mv go go_legacy
cd $HOME/go/go_legacy cd go_legacy
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x # this patch file only works on golang1.23.x

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@@ -55,7 +55,7 @@ jobs:
- name: Calculate version - name: Calculate version
if: github.event_name != 'workflow_dispatch' if: github.event_name != 'workflow_dispatch'
run: |- run: |-
go run -v ./cmd/internal/read_tag --nightly go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs - name: Set outputs
id: outputs id: outputs
run: |- run: |-
@@ -69,72 +69,48 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- name: linux_386 - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
goos: linux - { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
goarch: 386 - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- name: linux_amd64 - { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
goos: linux - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
goarch: amd64 - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- name: linux_arm64 - { 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" }
goos: linux - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
goarch: arm64 - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- name: linux_arm - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
goos: linux - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
goarch: arm - { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
goarm: 6 - { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
- name: linux_arm_v7 - { os: linux, arch: s390x, debian: s390x, rpm: s390x }
goos: linux - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
goarch: arm - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
goarm: 7 - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- name: linux_s390x
goos: linux - { os: windows, arch: amd64 }
goarch: s390x - { os: windows, arch: amd64, legacy_go: true }
- name: linux_riscv64 - { os: windows, arch: "386" }
goos: linux - { os: windows, arch: "386", legacy_go: true }
goarch: riscv64 - { os: windows, arch: arm64 }
- name: linux_mips64le
goos: linux - { os: darwin, arch: amd64 }
goarch: mips64le - { os: darwin, arch: arm64 }
- name: windows_amd64
goos: windows - { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
goarch: amd64 - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
require_legacy_go: true - { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- name: windows_386 - { os: android, arch: "386", ndk: "i686-linux-android21" }
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Cache legacy Go - name: Cache Legacy Go
if: matrix.require_legacy_go if: matrix.require_legacy_go
id: cache-legacy-go id: cache-legacy-go
uses: actions/cache@v4 uses: actions/cache@v4
@@ -142,64 +118,167 @@ jobs:
path: | path: |
~/go/go_legacy ~/go/go_legacy
key: go_legacy_1236 key: go_legacy_1236
- name: Setup legacy Go - name: Setup Legacy Go
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: bash .github/setup_legacy_go.sh 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 - name: Setup Android NDK
if: matrix.goos == 'android' if: matrix.os == 'android'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28 ndk-version: r28
local-cache: true local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: '~> v2'
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" 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 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 - name: Build
if: matrix.goos != 'android' if: matrix.os != 'android'
run: |- run: |
goreleaser release --clean --split 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: env:
GOOS: ${{ matrix.goos }} CGO_ENABLED: "0"
GOARCH: ${{ matrix.goarch }} GOOS: ${{ matrix.os }}
GOPATH: ${{ env.HOME }}/go GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }} GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Build Android - name: Build Android
if: matrix.goos == 'android' if: matrix.os == 'android'
run: |- run: |
set -xeuo pipefail
go install -v ./cmd/internal/build go install -v ./cmd/internal/build
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split 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: env:
BUILD_GOOS: ${{ matrix.goos }} CGO_ENABLED: "1"
BUILD_GOARCH: ${{ matrix.goarch }} BUILD_GOOS: ${{ matrix.os }}
GOARM: ${{ matrix.goarm }} BUILD_GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - name: Set name
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key run: |-
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 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 - name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-${{ matrix.name }} 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' path: "dist"
build_android: build_android:
name: Build Android name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
@@ -215,7 +294,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -271,13 +350,11 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload - name: Prepare upload
if: github.event_name == 'workflow_dispatch'
run: |- run: |-
mkdir -p dist/release mkdir -p dist
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact - name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-android-apks name: binary-android-apks
@@ -297,7 +374,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -395,7 +472,7 @@ jobs:
if: matrix.if if: matrix.if
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Setup Xcode stable - name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next' if: matrix.if && github.ref == 'refs/heads/main-next'
run: |- run: |-
@@ -472,10 +549,13 @@ jobs:
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version) 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"
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV" 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 - name: Build
if: matrix.if if: matrix.if
run: |- run: |-
go run -v ./cmd/internal/update_apple_version --ci
cd clients/apple cd clients/apple
xcodebuild archive \ xcodebuild archive \
-scheme "${{ matrix.scheme }}" \ -scheme "${{ matrix.scheme }}" \
@@ -524,9 +604,9 @@ jobs:
zip -r SFM.dSYMs.zip dSYMs zip -r SFM.dSYMs.zip dSYMs
popd popd
mkdir -p dist/release mkdir -p dist
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg" cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip" cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image - name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -547,12 +627,6 @@ jobs:
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: '~> v2'
install-only: true
- name: Cache ghr - name: Cache ghr
uses: actions/cache@v4 uses: actions/cache@v4
id: cache-ghr id: cache-ghr
@@ -577,26 +651,17 @@ jobs:
with: with:
path: dist path: dist
merge-multiple: true merge-multiple: true
- name: Merge builds
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
run: |-
goreleaser continue --merge --skip publish
mkdir -p dist/release
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds - name: Upload builds
if: ${{ env.PUBLISHED == 'false' }} if: ${{ env.PUBLISHED == 'false' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds - name: Replace builds
if: ${{ env.PUBLISHED != 'false' }} if: ${{ env.PUBLISHED != 'false' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist/release ghr --replace -p 5 "v${VERSION}" dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -28,10 +28,11 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v6 uses: golangci/golangci-lint-action@v6
with: with:
version: latest version: latest
args: --timeout=30m args: --timeout=30m
install-mode: binary install-mode: binary
verify: false

View File

@@ -1,13 +1,22 @@
name: Release to Linux repository name: Build Linux Packages
on: on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release: release:
types: types:
- published - published
jobs: jobs:
build: calculate_version:
name: Calculate version
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -16,23 +25,160 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.3
- name: Extract signing key - name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |- run: |-
mkdir -p $HOME/.gnupg echo "version=${{ inputs.version }}"
cat > $HOME/.gnupg/sagernet.key <<EOF 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'
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 }} ${{ secrets.GPG_KEY }}
EOF EOF
echo "HOME=$HOME" >> "$GITHUB_ENV" debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Publish release - name: Package RPM
uses: goreleaser/goreleaser-action@v6 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: with:
distribution: goreleaser-pro name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
version: '~> v2' path: "dist"
args: release -f .goreleaser.fury.yaml --clean upload:
env: name: Upload builds
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} runs-on: ubuntu-latest
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} needs:
FURY_TOKEN: ${{ secrets.FURY_TOKEN }} - calculate_version
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key - build
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 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/

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
/.idea/ /.idea/
/vendor/ /vendor/
/*.json /*.json
/*.js
/*.srs /*.srs
/*.db /*.db
/site/ /site/

View File

@@ -21,17 +21,15 @@ linters-settings:
- -SA1003 - -SA1003
run: run:
go: "1.24" go: "1.23"
build-tags: build-tags:
- with_gvisor - with_gvisor
- with_quic - with_quic
- with_dhcp - with_dhcp
- with_wireguard - with_wireguard
- with_utls - with_utls
- with_reality_server
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_script
issues: issues:
exclude-dirs: exclude-dirs:

View File

@@ -15,7 +15,6 @@ builds:
- with_dhcp - with_dhcp
- with_wireguard - with_wireguard
- with_utls - with_utls
- with_reality_server
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
@@ -50,7 +49,7 @@ nfpms:
contents: contents:
- src: release/config/config.json - src: release/config/config.json
dst: /etc/sing-box/config.json dst: /etc/sing-box/config.json
type: config type: "config|noreplace"
- src: release/config/sing-box.service - src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -17,11 +17,9 @@ builds:
- with_dhcp - with_dhcp
- with_wireguard - with_wireguard
- with_utls - with_utls
- with_reality_server
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
- with_script
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
- GOTOOLCHAIN=local - GOTOOLCHAIN=local
@@ -48,11 +46,9 @@ builds:
- with_dhcp - with_dhcp
- with_wireguard - with_wireguard
- with_utls - with_utls
- with_reality_server
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
- with_script
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go_legacy - GOROOT={{ .Env.GOPATH }}/go_legacy
@@ -128,13 +124,13 @@ nfpms:
- deb - deb
- rpm - rpm
- archlinux - archlinux
# - apk # - apk
# - ipk # - ipk
priority: extra priority: extra
contents: contents:
- src: release/config/config.json - src: release/config/config.json
dst: /etc/sing-box/config.json dst: /etc/sing-box/config.json
type: config type: "config|noreplace"
- src: release/config/sing-box.service - src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -13,7 +13,7 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \ && export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \ && export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \ && go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \ "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
-o /go/bin/sing-box \ -o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box ./cmd/sing-box

View File

@@ -1,16 +1,14 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_tailscale
TAGS_GO123 = with_tailscale,with_script TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls
TAGS ?= $(TAGS_GO120),$(TAGS_GO123)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH) GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid=" PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags $(TAGS) MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH) PREFIX ?= $(shell go env GOPATH)
@@ -20,18 +18,13 @@ build:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
ci_build_go120:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
ci_build: ci_build:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \ go build $(PARAMS) $(MAIN) && \
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
generate_completions: generate_completions:
go run -v --tags $(TAGS),generate,generate_completions $(MAIN) go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
install: install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
@@ -233,8 +226,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios
lib_install: lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.5 go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
docs: docs:
venv/bin/mkdocs serve venv/bin/mkdocs serve

View File

@@ -10,9 +10,6 @@ import (
type CertificateStore interface { type CertificateStore interface {
LifecycleService LifecycleService
Pool() *x509.CertPool Pool() *x509.CertPool
TLSDecryptionEnabled() bool
TLSDecryptionCertificate() *x509.Certificate
TLSDecryptionPrivateKey() any
} }
func RootPoolFromContext(ctx context.Context) *x509.CertPool { func RootPoolFromContext(ctx context.Context) *x509.CertPool {

View File

@@ -52,10 +52,6 @@ type CacheFile interface {
StoreGroupExpand(group string, expand bool) error StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedBinary LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error SaveRuleSet(tag string, set *SavedBinary) error
LoadScript(tag string) *SavedBinary
SaveScript(tag string, script *SavedBinary) error
SurgePersistentStoreRead(key string) string
SurgePersistentStoreWrite(key string, value string) error
} }
type SavedBinary struct { type SavedBinary struct {

View File

@@ -2,8 +2,6 @@ package adapter
import ( import (
"context" "context"
"crypto/tls"
"net/http"
"net/netip" "net/netip"
"time" "time"
@@ -60,8 +58,6 @@ type InboundContext struct {
Client string Client string
SniffContext any SniffContext any
PacketSniffError error PacketSniffError error
HTTPRequest *http.Request
ClientHello *tls.ClientHelloInfo
// cache // cache
@@ -78,7 +74,7 @@ type InboundContext struct {
UDPTimeout time.Duration UDPTimeout time.Duration
TLSFragment bool TLSFragment bool
TLSFragmentFallbackDelay time.Duration TLSFragmentFallbackDelay time.Duration
MITM *option.MITMRouteOptions TLSRecordFragment bool
NetworkStrategy *C.NetworkStrategy NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType NetworkType []C.InterfaceType

View File

@@ -1,8 +1,6 @@
package adapter package adapter
import ( import E "github.com/sagernet/sing/common/exceptions"
E "github.com/sagernet/sing/common/exceptions"
)
type StartStage uint8 type StartStage uint8
@@ -47,9 +45,6 @@ type LifecycleService interface {
func Start(stage StartStage, services ...Lifecycle) error { func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services { for _, service := range services {
if service == nil {
continue
}
err := service.Start(stage) err := service.Start(stage)
if err != nil { if err != nil {
return err return err

View File

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

View File

@@ -24,7 +24,7 @@ type Router interface {
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool NeedWIFIState() bool
Rules() []Rule Rules() []Rule
SetTracker(tracker ConnectionTracker) AppendTracker(tracker ConnectionTracker)
ResetNetwork() ResetNetwork()
} }

View File

@@ -1,54 +0,0 @@
package adapter
import (
"context"
"net/http"
"sync"
"time"
)
type ScriptManager interface {
Lifecycle
Scripts() []Script
Script(name string) (Script, bool)
SurgeCache() *SurgeInMemoryCache
}
type SurgeInMemoryCache struct {
sync.RWMutex
Data map[string]string
}
type Script interface {
Type() string
Tag() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Close() error
}
type SurgeScript interface {
Script
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
}
type HTTPRequestScriptResult struct {
URL string
Headers http.Header
Body []byte
Response *HTTPRequestScriptResponse
}
type HTTPRequestScriptResponse struct {
Status int
Headers http.Header
Body []byte
}
type HTTPResponseScriptResult struct {
Status int
Headers http.Header
Body []byte
}

55
box.go
View File

@@ -23,11 +23,9 @@ import (
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform" "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/mitm"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/script"
"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"
@@ -50,8 +48,6 @@ type Box struct {
dnsRouter *dns.Router dnsRouter *dns.Router
connection *route.ConnectionManager connection *route.ConnectionManager
router *route.Router router *route.Router
script *script.Manager
mitm adapter.MITMEngine //*mitm.Engine
services []adapter.LifecycleService services []adapter.LifecycleService
done chan struct{} done chan struct{}
} }
@@ -147,12 +143,18 @@ func New(options Options) (*Box, error) {
} }
var services []adapter.LifecycleService var services []adapter.LifecycleService
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate)) certificateOptions := common.PtrValueOrDefault(options.Certificate)
if err != nil { if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
return nil, err len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
} }
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
routeOptions := common.PtrValueOrDefault(options.Route) routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS) dnsOptions := common.PtrValueOrDefault(options.DNS)
@@ -171,7 +173,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager") return nil, E.Cause(err, "initialize network manager")
} }
service.MustRegister[adapter.NetworkManager](ctx, networkManager) service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection")) connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router) service.MustRegister[adapter.Router](ctx, router)
@@ -179,8 +181,8 @@ func New(options Options) (*Box, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize router") return nil, E.Cause(err, "initialize router")
} }
var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP) ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled { if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper) timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService) service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -294,11 +296,6 @@ func New(options Options) (*Box, error) {
"local", "local",
option.LocalDNSServerOptions{}, option.LocalDNSServerOptions{},
))) )))
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
if err != nil {
return nil, E.Cause(err, "initialize script manager")
}
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
if platformInterface != nil { if platformInterface != nil {
err = platformInterface.Initialize(networkManager) err = platformInterface.Initialize(networkManager)
if err != nil { if err != nil {
@@ -317,7 +314,7 @@ func New(options Options) (*Box, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create clash-server") return nil, E.Cause(err, "create clash-server")
} }
router.SetTracker(clashServer) router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer) service.MustRegister[adapter.ClashServer](ctx, clashServer)
services = append(services, clashServer) services = append(services, clashServer)
} }
@@ -327,7 +324,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create v2ray-server") return nil, E.Cause(err, "create v2ray-server")
} }
if v2rayServer.StatsService() != nil { if v2rayServer.StatsService() != nil {
router.SetTracker(v2rayServer.StatsService()) router.AppendTracker(v2rayServer.StatsService())
services = append(services, v2rayServer) services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
} }
@@ -348,16 +345,6 @@ func New(options Options) (*Box, error) {
timeService.TimeService = ntpService timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
} }
mitmOptions := common.PtrValueOrDefault(options.MITM)
var mitmEngine adapter.MITMEngine
if mitmOptions.Enabled {
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
if err != nil {
return nil, E.Cause(err, "create MITM engine")
}
service.MustRegister[adapter.MITMEngine](ctx, engine)
mitmEngine = engine
}
return &Box{ return &Box{
network: networkManager, network: networkManager,
endpoint: endpointManager, endpoint: endpointManager,
@@ -367,8 +354,6 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter, dnsRouter: dnsRouter,
connection: connectionManager, connection: connectionManager,
router: router, router: router,
script: scriptManager,
mitm: mitmEngine,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
@@ -427,11 +412,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm) err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil { if err != nil {
return err return err
} }
@@ -455,7 +440,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -463,7 +448,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -482,7 +467,7 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := common.Close( err := common.Close(
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -59,7 +59,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") 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) 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_script") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
memcTags = append(memcTags, "with_tailscale") memcTags = append(memcTags, "with_tailscale")
debugTags = append(debugTags, "debug") debugTags = append(debugTags, "debug")

View File

@@ -5,40 +5,49 @@ import (
"os" "os"
"github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
) )
var nightly bool var (
flagRunInCI bool
flagRunNightly bool
)
func init() { func init() {
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag") flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
} }
func main() { func main() {
flag.Parse() flag.Parse()
if nightly { var (
version, err := build_shared.ReadTagVersionRev() 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var versionStr string
if version.PreReleaseIdentifier != "" {
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr) err = setGitHubEnv("version", versionStr)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
tag, err := build_shared.ReadTag()
if err != nil { if err != nil {
log.Error(err) log.Error(err)
os.Stdout.WriteString("unknown\n") os.Stdout.WriteString("unknown\n")
} else { } else {
os.Stdout.WriteString(tag + "\n") os.Stdout.WriteString(versionStr + "\n")
} }
} }
} }

View File

@@ -1,121 +0,0 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"math/big"
"os"
"path/filepath"
"strings"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
"software.sslmate.com/src/go-pkcs12"
)
var (
flagGenerateCAName string
flagGenerateCAPKCS12Password string
flagGenerateOutput string
)
var commandGenerateCAKeyPair = &cobra.Command{
Use: "ca-keypair",
Short: "Generate CA key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateCAKeyPair()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAName, "name", "n", "", "Set custom CA name")
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAPKCS12Password, "p12-password", "p", "", "Set custom PKCS12 password")
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateOutput, "output", "o", ".", "Set output directory")
commandGenerate.AddCommand(commandGenerateCAKeyPair)
}
func generateCAKeyPair() error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return err
}
spkiASN1, err := x509.MarshalPKIXPublicKey(privateKey.Public())
var spki struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
_, err = asn1.Unmarshal(spkiASN1, &spki)
if err != nil {
return err
}
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
var caName string
if flagGenerateCAName != "" {
caName = flagGenerateCAName
} else {
caName = "sing-box Generated CA " + strings.ToUpper(hex.EncodeToString(skid[:4]))
}
caTpl := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{caName},
CommonName: caName,
},
SubjectKeyId: skid[:],
NotAfter: time.Now().AddDate(10, 0, 0),
NotBefore: time.Now(),
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: true,
}
publicDer, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, privateKey.Public(), privateKey)
var caPassword string
if flagGenerateCAPKCS12Password != "" {
caPassword = flagGenerateCAPKCS12Password
} else {
caPassword = strings.ToUpper(hex.EncodeToString(skid[:4]))
}
caTpl.Raw = publicDer
p12Bytes, err := pkcs12.Modern.Encode(privateKey, caTpl, nil, caPassword)
if err != nil {
return err
}
privateDer, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return err
}
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".private.pem"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer}), 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".crt"), publicDer, 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".p12"), p12Bytes, 0o644)
var tlsDecryptionOptions option.TLSDecryptionOptions
tlsDecryptionOptions.Enabled = true
tlsDecryptionOptions.KeyPair = base64.StdEncoding.EncodeToString(p12Bytes)
tlsDecryptionOptions.KeyPairPassword = caPassword
var certificateOptions option.CertificateOptions
certificateOptions.TLSDecryption = &tlsDecryptionOptions
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(certificateOptions)
}

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"io" "io"
"os" "os"
"path/filepath"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs" "github.com/sagernet/sing-box/common/srs"
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil { if err != nil {
return E.Cause(err, "read rule-set") 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 var ruleSet option.PlainRuleSetCompat
switch flagRuleSetMatchFormat { switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource: case C.RuleSetFormatSource:

View File

@@ -1,6 +1,13 @@
package main package main
import ( import (
"errors"
"os"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -12,5 +19,36 @@ var commandTools = &cobra.Command{
} }
func init() { func init() {
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
mainCommand.AddCommand(commandTools) mainCommand.AddCommand(commandTools)
} }
func createPreStartedClient() (*box.Box, error) {
options, err := readConfigAndMerge()
if err != nil {
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
return nil, err
}
}
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
if err != nil {
return nil, E.Cause(err, "create service")
}
err = instance.PreStart()
if err != nil {
return nil, E.Cause(err, "start service")
}
return instance, nil
}
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
if outboundTag == "" {
return instance.Outbound().Default(), nil
} else {
outbound, loaded := instance.Outbound().Outbound(outboundTag)
if !loaded {
return nil, E.New("outbound not found: ", outboundTag)
}
return outbound, nil
}
}

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
//go:build with_quic
package main
import (
"context"
"crypto/tls"
"net/http"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
box "github.com/sagernet/sing-box"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func initializeHTTP3Client(instance *box.Box) error {
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}
http3Client = &http.Client{
Transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
if dErr != nil {
return nil, dErr
}
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
},
},
}
return nil
}

View File

@@ -0,0 +1,18 @@
//go:build !with_quic
package main
import (
"net/url"
"os"
box "github.com/sagernet/sing-box"
)
func initializeHTTP3Client(instance *box.Box) error {
return os.ErrInvalid
}
func fetchHTTP3(parsedURL *url.URL) error {
return os.ErrInvalid
}

View File

@@ -1,108 +0,0 @@
package main
import (
"encoding/pem"
"errors"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/shell"
"github.com/spf13/cobra"
)
var commandInstallCACertificate = &cobra.Command{
Use: "install-ca <path to certificate>",
Short: "Install CA certificate to system",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := installCACertificate(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandTools.AddCommand(commandInstallCACertificate)
}
func installCACertificate(path string) error {
switch runtime.GOOS {
case "windows":
return shell.Exec("powershell", "-Command", "Import-Certificate -FilePath \""+path+"\" -CertStoreLocation Cert:\\LocalMachine\\Root").Attach().Run()
case "darwin":
return shell.Exec("sudo", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", path).Attach().Run()
case "linux":
updateCertPath, updateCertPathNotFoundErr := exec.LookPath("update-ca-certificates")
if updateCertPathNotFoundErr == nil {
publicDer, err := os.ReadFile(path)
if err != nil {
return err
}
err = os.MkdirAll("/usr/local/share/ca-certificates", 0o755)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
fileName := filepath.Base(updateCertPath)
if !strings.HasSuffix(fileName, ".crt") {
fileName = fileName + ".crt"
}
filePath, _ := filepath.Abs(filepath.Join("/usr/local/share/ca-certificates", fileName))
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
log.Info("certificate written to " + filePath + "\n")
err = shell.Exec(updateCertPath).Attach().Run()
if err != nil {
return err
}
log.Info("certificate installed")
return nil
}
updateTrustPath, updateTrustPathNotFoundErr := exec.LookPath("update-ca-trust")
if updateTrustPathNotFoundErr == nil {
publicDer, err := os.ReadFile(path)
if err != nil {
return err
}
fileName := filepath.Base(updateTrustPath)
fileExt := filepath.Ext(path)
if fileExt != "" {
fileName = fileName[:len(fileName)-len(fileExt)]
}
filePath, _ := filepath.Abs(filepath.Join("/etc/pki/ca-trust/source/anchors/", fileName+".pem"))
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
log.Info("certificate written to " + filePath + "\n")
err = shell.Exec(updateTrustPath, "extract").Attach().Run()
if err != nil {
return err
}
log.Info("certificate installed")
}
return E.New("update-ca-certificates or update-ca-trust not found")
default:
return E.New("unsupported operating system: ", runtime.GOOS)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -40,11 +39,20 @@ func init() {
} }
func syncTime() error { func syncTime() error {
instance, err := createPreStartedClient()
if err != nil {
return err
}
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}
defer instance.Close()
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer) serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
if serverAddress.Port == 0 { if serverAddress.Port == 0 {
serverAddress.Port = 123 serverAddress.Port = 123
} }
response, err := ntp.Exchange(context.Background(), N.SystemDialer, serverAddress) response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -7,7 +7,8 @@ import (
_ "unsafe" _ "unsafe"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/utls"
"github.com/metacubex/utls"
) )
func init() { func init() {
@@ -24,8 +25,8 @@ func init() {
}) })
} }
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord //go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
func utlsReadRecord(c *tls.Conn) error func utlsReadRecord(c *tls.Conn) error
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage //go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
func utlsHandlePostHandshakeMessage(c *tls.Conn) error func utlsHandlePostHandshakeMessage(c *tls.Conn) error

View File

@@ -3,7 +3,6 @@ package certificate
import ( import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/base64"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
@@ -17,8 +16,6 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"software.sslmate.com/src/go-pkcs12"
) )
var _ adapter.CertificateStore = (*Store)(nil) var _ adapter.CertificateStore = (*Store)(nil)
@@ -30,9 +27,6 @@ type Store struct {
certificatePaths []string certificatePaths []string
certificateDirectoryPaths []string certificateDirectoryPaths []string
watcher *fswatch.Watcher watcher *fswatch.Watcher
tlsDecryptionEnabled bool
tlsDecryptionPrivateKey any
tlsDecryptionCertificate *x509.Certificate
} }
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) { func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
@@ -96,19 +90,6 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
if err != nil { if err != nil {
return nil, E.Cause(err, "initializing certificate store") return nil, E.Cause(err, "initializing certificate store")
} }
if options.TLSDecryption != nil && options.TLSDecryption.Enabled {
pfxBytes, err := base64.StdEncoding.DecodeString(options.TLSDecryption.KeyPair)
if err != nil {
return nil, E.Cause(err, "decode key pair base64 bytes")
}
privateKey, certificate, err := pkcs12.Decode(pfxBytes, options.TLSDecryption.KeyPairPassword)
if err != nil {
return nil, E.Cause(err, "decode key pair")
}
store.tlsDecryptionEnabled = true
store.tlsDecryptionPrivateKey = privateKey
store.tlsDecryptionCertificate = certificate
}
return store, nil return store, nil
} }
@@ -202,15 +183,3 @@ func isSameDirSymlink(f fs.DirEntry, dir string) bool {
target, err := os.Readlink(filepath.Join(dir, f.Name())) target, err := os.Readlink(filepath.Join(dir, f.Name()))
return err == nil && !strings.Contains(target, "/") return err == nil && !strings.Contains(target, "/")
} }
func (s *Store) TLSDecryptionEnabled() bool {
return s.tlsDecryptionEnabled
}
func (s *Store) TLSDecryptionCertificate() *x509.Certificate {
return s.tlsDecryptionCertificate
}
func (s *Store) TLSDecryptionPrivateKey() any {
return s.tlsDecryptionPrivateKey
}

View File

@@ -71,18 +71,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} }
if options.RoutingMark > 0 { if options.RoutingMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark))) dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark))) listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
}
if networkManager != nil {
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
if autoRedirectOutputMark > 0 {
if options.RoutingMark > 0 {
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
}
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
} }
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
if disableDefaultBind || options.TCPFastOpen { if disableDefaultBind || options.TCPFastOpen {
@@ -127,8 +117,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
} }
} }
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark)) dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark)) listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
} }
} }
if options.ReuseAddr { if options.ReuseAddr {
@@ -210,6 +200,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}, nil }, nil
} }
func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {
if networkManager == nil {
return control.RoutingMark(mark)
}
return func(network, address string, conn syscall.RawConn) error {
if networkManager.AutoRedirectOutputMark() != 0 {
if isDefault {
return E.New("`route.default_mark` is conflict with `tun.auto_redirect`")
} else {
return E.New("`routing_mark` is conflict with `tun.auto_redirect`")
}
}
return control.RoutingMark(mark)(network, address, conn)
}
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
if !address.IsValid() { if !address.IsValid() {
return nil, E.New("invalid address") return nil, E.New("invalid address")
@@ -335,7 +341,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
} }
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return d.udpListener.ListenPacket(context.Background(), network, address) udpListener := d.udpListener
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
for _, wgControlFn := range WgControlFns {
err := wgControlFn(network, address, conn)
if err != nil {
return err
}
}
return nil
})
return udpListener.ListenPacket(context.Background(), network, address)
} }
func trackConn(conn net.Conn, err error) (net.Conn, error) { func trackConn(conn net.Conn, err error) (net.Conn, error) {

View File

@@ -19,14 +19,18 @@ type DirectDialer interface {
type DetourDialer struct { type DetourDialer struct {
outboundManager adapter.OutboundManager outboundManager adapter.OutboundManager
detour string detour string
directResolver bool legacyDNSDialer bool
dialer N.Dialer dialer N.Dialer
initOnce sync.Once initOnce sync.Once
initErr error initErr error
} }
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer { func NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer {
return &DetourDialer{outboundManager: outboundManager, detour: detour} return &DetourDialer{
outboundManager: outboundManager,
detour: detour,
legacyDNSDialer: legacyDNSDialer,
}
} }
func InitializeDetour(dialer N.Dialer) error { func InitializeDetour(dialer N.Dialer) error {
@@ -48,10 +52,12 @@ func (d *DetourDialer) init() {
d.initErr = E.New("outbound detour not found: ", d.detour) d.initErr = E.New("outbound detour not found: ", d.detour)
return return
} }
if directDialer, isDirect := dialer.(DirectDialer); isDirect { if !d.legacyDNSDialer {
if directDialer.IsEmpty() { if directDialer, isDirect := dialer.(DirectDialer); isDirect {
d.initErr = E.New("detour to an empty direct outbound makes no sense") if directDialer.IsEmpty() {
return d.initErr = E.New("detour to an empty direct outbound makes no sense")
return
}
} }
} }
d.dialer = dialer d.dialer = dialer

View File

@@ -23,6 +23,8 @@ type Options struct {
DirectResolver bool DirectResolver bool
ResolverOnDetour bool ResolverOnDetour bool
NewDialer bool NewDialer bool
LegacyDNSDialer bool
DirectOutbound bool
} }
// TODO: merge with NewWithOptions // TODO: merge with NewWithOptions
@@ -45,7 +47,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
if outboundManager == nil { if outboundManager == nil {
return nil, E.New("missing outbound manager") return nil, E.New("missing outbound manager")
} }
dialer = NewDetour(outboundManager, dialOptions.Detour) dialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer)
} else { } else {
dialer, err = NewDefault(options.Context, dialOptions) dialer, err = NewDefault(options.Context, dialOptions)
if err != nil { if err != nil {
@@ -81,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck //nolint:staticcheck
strategy = C.DomainStrategy(dialOptions.DomainStrategy) strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
} }
server = dialOptions.DomainResolver.Server server = dialOptions.DomainResolver.Server
dnsQueryOptions = adapter.DNSQueryOptions{ dnsQueryOptions = adapter.DNSQueryOptions{
@@ -93,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.DirectResolver { } else if options.DirectResolver {
return nil, E.New("missing domain resolver for domain server address") return nil, E.New("missing domain resolver for domain server address")
} else if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else { } else {
transports := dnsTransport.Transports() if defaultOptions.DomainResolver != "" {
if len(transports) < 2 { dnsQueryOptions = defaultOptions.DomainResolveOptions
dnsQueryOptions.Transport = dnsTransport.Default() transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else { } else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else if !options.DirectOutbound {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
}
if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
} }
} }
dialer = NewResolveDialer( dialer = NewResolveDialer(

View File

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

View File

@@ -8,9 +8,11 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/metacubex/tfo-go" "github.com/metacubex/tfo-go"
) )
@@ -23,6 +25,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
var err error var err error
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var listenConfig net.ListenConfig var listenConfig net.ListenConfig
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
if l.listenOptions.TCPKeepAlive >= 0 { if l.listenOptions.TCPKeepAlive >= 0 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 { if keepIdle == 0 {

View File

@@ -6,16 +6,27 @@ import (
"net/netip" "net/netip"
"os" "os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
func (l *Listener) ListenUDP() (net.PacketConn, error) { func (l *Listener) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var lc net.ListenConfig var listenConfig net.ListenConfig
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
var udpFragment bool var udpFragment bool
if l.listenOptions.UDPFragment != nil { if l.listenOptions.UDPFragment != nil {
udpFragment = *l.listenOptions.UDPFragment udpFragment = *l.listenOptions.UDPFragment
@@ -23,10 +34,10 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
udpFragment = l.listenOptions.UDPFragmentDefault udpFragment = l.listenOptions.UDPFragmentDefault
} }
if !udpFragment { if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
} }
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -37,9 +48,32 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
return udpConn, err return udpConn, err
} }
func (l *Listener) ListenPacket(ctx context.Context, network string, address string) (net.PacketConn, error) { func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
if l.listenOptions.BindInterface != "" {
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
}
return dialer.DialContext(ctx, network, address)
})
}
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
var listenConfig net.ListenConfig if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
return listenConfig.ListenPacket(ctx, network, address) return listenConfig.ListenPacket(ctx, network, address)
}) })
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
) )
const ( const (
@@ -23,21 +24,26 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
var first byte var first byte
err := binary.Read(reader, binary.BigEndian, &first) err := binary.Read(reader, binary.BigEndian, &first)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if first != 19 { if first != 19 {
return os.ErrInvalid return os.ErrInvalid
} }
const header = "BitTorrent protocol"
var protocol [19]byte var protocol [19]byte
_, err = reader.Read(protocol[:]) var n int
if err != nil { n, err = reader.Read(protocol[:])
return err if string(protocol[:n]) != header[:n] {
}
if string(protocol[:]) != "BitTorrent protocol" {
return os.ErrInvalid return os.ErrInvalid
} }
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if n < 19 {
return ErrNeedMoreData
}
metadata.Protocol = C.ProtocolBitTorrent metadata.Protocol = C.ProtocolBitTorrent
return nil return nil
@@ -67,7 +73,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
if err != nil { if err != nil {
return err return err
} }
if extension > 0x04 {
return os.ErrInvalid
}
var length byte var length byte
err = binary.Read(reader, binary.BigEndian, &length) err = binary.Read(reader, binary.BigEndian, &length)
if err != nil { if err != nil {

View File

@@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
} }
} }
func TestSniffIncompleteBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e74")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e75")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffUTP(t *testing.T) { func TestSniffUTP(t *testing.T) {
t.Parallel() t.Parallel()
@@ -71,3 +92,19 @@ func TestSniffUDPTracker(t *testing.T) {
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol) require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
} }
} }
func TestSniffNotUTP(t *testing.T) {
t.Parallel()
packets := []string{
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.UTP(context.TODO(), &metadata, pkt)
require.Error(t, err)
}
}

View File

@@ -5,14 +5,11 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"os" "os"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
@@ -21,35 +18,40 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
var length uint16 var length uint16
err := binary.Read(reader, binary.BigEndian, &length) err := binary.Read(reader, binary.BigEndian, &length)
if err != nil { if err != nil {
return os.ErrInvalid return E.Cause1(ErrNeedMoreData, err)
} }
if length == 0 { if length < 12 {
return os.ErrInvalid return os.ErrInvalid
} }
buffer := buf.NewSize(int(length)) buffer := buf.NewSize(int(length))
defer buffer.Release() defer buffer.Release()
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100) var n int
var readTask task.Group n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
readTask.Append0(func(ctx context.Context) error { packet := buffer.Bytes()
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen())) if n > 2 && packet[2]&0x80 != 0 { // QR
}) return os.ErrInvalid
err = readTask.Run(readCtx)
cancel()
if err != nil {
return err
} }
return DomainNameQuery(readCtx, metadata, buffer.Bytes()) if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
return os.ErrInvalid
}
for i := 6; i < 10; i++ {
// ANCOUNT, NSCOUNT
if n > i && packet[i] != 0 {
return os.ErrInvalid
}
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
return DomainNameQuery(readCtx, metadata, packet)
} }
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
var msg mDNS.Msg var msg mDNS.Msg
err := msg.Unpack(packet) err := msg.Unpack(packet)
if err != nil { if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
return err return err
} }
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolDNS metadata.Protocol = C.ProtocolDNS
return nil return nil
} }

53
common/sniff/dns_test.go Normal file
View File

@@ -0,0 +1,53 @@
package sniff_test
import (
"bytes"
"context"
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffIncompleteStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000000000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

View File

@@ -3,10 +3,12 @@ package sniff
import ( import (
std_bufio "bufio" std_bufio "bufio"
"context" "context"
"errors"
"io" "io"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/http"
) )
@@ -14,10 +16,13 @@ import (
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
request, err := http.ReadRequest(std_bufio.NewReader(reader)) request, err := http.ReadRequest(std_bufio.NewReader(reader))
if err != nil { if err != nil {
return err if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
} }
metadata.Protocol = C.ProtocolHTTP metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
metadata.HTTPRequest = request
return nil return nil
} }

View File

@@ -20,8 +20,6 @@ import (
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
) )
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
reader := bytes.NewReader(packet) reader := bytes.NewReader(packet)
typeByte, err := reader.ReadByte() typeByte, err := reader.ReadByte()
@@ -308,7 +306,7 @@ find:
metadata.Protocol = C.ProtocolQUIC metadata.Protocol = C.ProtocolQUIC
metadata.Client = C.ClientChromium metadata.Client = C.ClientChromium
metadata.SniffContext = fragments metadata.SniffContext = fragments
return ErrClientHelloFragmented return E.Cause1(ErrNeedMoreData, err)
} }
metadata.Domain = fingerprint.ServerName metadata.Domain = fingerprint.ServerName
for metadata.Client == "" { for metadata.Client == "" {

View File

@@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium) require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894") pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f") pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
@@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium) require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28") pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
) )
@@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktVersion uint8 var tpktVersion uint8
err := binary.Read(reader, binary.BigEndian, &tpktVersion) err := binary.Read(reader, binary.BigEndian, &tpktVersion)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktVersion != 0x03 { if tpktVersion != 0x03 {
return os.ErrInvalid return os.ErrInvalid
@@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktReserved uint8 var tpktReserved uint8
err = binary.Read(reader, binary.BigEndian, &tpktReserved) err = binary.Read(reader, binary.BigEndian, &tpktReserved)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktReserved != 0x00 { if tpktReserved != 0x00 {
return os.ErrInvalid return os.ErrInvalid
@@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktLength uint16 var tpktLength uint16
err = binary.Read(reader, binary.BigEndian, &tpktLength) err = binary.Read(reader, binary.BigEndian, &tpktLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktLength != 19 { if tpktLength != 19 {
@@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpLength uint8 var cotpLength uint8
err = binary.Read(reader, binary.BigEndian, &cotpLength) err = binary.Read(reader, binary.BigEndian, &cotpLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if cotpLength != 14 { if cotpLength != 14 {
@@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpTpduType uint8 var cotpTpduType uint8
err = binary.Read(reader, binary.BigEndian, &cotpTpduType) err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if cotpTpduType != 0xE0 { if cotpTpduType != 0xE0 {
return os.ErrInvalid return os.ErrInvalid
@@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
err = rw.SkipN(reader, 5) err = rw.SkipN(reader, 5)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
var rdpType uint8 var rdpType uint8
err = binary.Read(reader, binary.BigEndian, &rdpType) err = binary.Read(reader, binary.BigEndian, &rdpType)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if rdpType != 0x01 { if rdpType != 0x01 {
return os.ErrInvalid return os.ErrInvalid
@@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var rdpFlags uint8 var rdpFlags uint8
err = binary.Read(reader, binary.BigEndian, &rdpFlags) err = binary.Read(reader, binary.BigEndian, &rdpFlags)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
var rdpLength uint8 var rdpLength uint8
err = binary.Read(reader, binary.BigEndian, &rdpLength) err = binary.Read(reader, binary.BigEndian, &rdpLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if rdpLength != 8 { if rdpLength != 8 {
return os.ErrInvalid return os.ErrInvalid

View File

@@ -3,6 +3,7 @@ package sniff
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"io" "io"
"net" "net"
"time" "time"
@@ -19,6 +20,8 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
) )
var ErrNeedMoreData = E.New("need more data")
func Skip(metadata *adapter.InboundContext) bool { func Skip(metadata *adapter.InboundContext) bool {
// skip server first protocols // skip server first protocols
switch metadata.Destination.Port { switch metadata.Destination.Port {
@@ -40,7 +43,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
timeout = C.ReadPayloadTimeout timeout = C.ReadPayloadTimeout
} }
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
var errors []error var sniffError error
for i := 0; ; i++ { for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline) err := conn.SetReadDeadline(deadline)
if err != nil { if err != nil {
@@ -54,7 +57,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
} }
return E.Cause(err, "read payload") return E.Cause(err, "read payload")
} }
errors = nil sniffError = nil
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes()) return bytes.NewReader(it.Bytes())
@@ -63,20 +66,23 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
if err == nil { if err == nil {
return nil return nil
} }
errors = append(errors, err) sniffError = E.Errors(sniffError, err)
}
if !errors.Is(sniffError, ErrNeedMoreData) {
break
} }
} }
return E.Errors(errors...) return sniffError
} }
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error { func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
var errors []error var sniffError []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
err := sniffer(ctx, metadata, packet) err := sniffer(ctx, metadata, packet)
if err == nil { if err == nil {
return nil return nil
} }
errors = append(errors, err) sniffError = append(sniffError, err)
} }
return E.Errors(errors...) return E.Errors(sniffError...)
} }

View File

@@ -5,22 +5,27 @@ import (
"context" "context"
"io" "io"
"os" "os"
"strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
) )
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
scanner := bufio.NewScanner(reader) const sshPrefix = "SSH-2.0-"
if !scanner.Scan() { bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if string(prefix[:]) != sshPrefix[:len(prefix)] {
return os.ErrInvalid return os.ErrInvalid
} }
fistLine := scanner.Text() if err != nil {
if !strings.HasPrefix(fistLine, "SSH-2.0-") { return E.Cause1(ErrNeedMoreData, err)
return os.ErrInvalid }
fistLine, _, err := bReader.ReadLine()
if err != nil {
return err
} }
metadata.Protocol = C.ProtocolSSH metadata.Protocol = C.ProtocolSSH
metadata.Client = fistLine[8:] metadata.Client = string(fistLine)[8:]
return nil return nil
} }

View File

@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
require.Equal(t, C.ProtocolSSH, metadata.Protocol) require.Equal(t, C.ProtocolSSH, metadata.Protocol)
require.Equal(t, "dropbear", metadata.Client) require.Equal(t, "dropbear", metadata.Client)
} }
func TestSniffIncompleteSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e30")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e31")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

View File

@@ -3,11 +3,13 @@ package sniff
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"io" "io"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
) )
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
@@ -21,8 +23,11 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
if clientHello != nil { if clientHello != nil {
metadata.Protocol = C.ProtocolTLS metadata.Protocol = C.ProtocolTLS
metadata.Domain = clientHello.ServerName metadata.Domain = clientHello.ServerName
metadata.ClientHello = clientHello
return nil return nil
} }
return err if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
} }

View File

@@ -18,6 +18,7 @@ type (
STDConfig = tls.Config STDConfig = tls.Config
STDConn = tls.Conn STDConn = tls.Conn
ConnectionState = tls.ConnectionState ConnectionState = tls.ConnectionState
CurveID = tls.CurveID
) )
func ParseTLSVersion(version string) (uint16, error) { func ParseTLSVersion(version string) (uint16, error) {

View File

@@ -10,6 +10,8 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
@@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
tlsConfig.EncryptedClientHelloConfigList = block.Bytes tlsConfig.EncryptedClientHelloConfigList = block.Bytes
return &STDClientConfig{tlsConfig}, nil return &STDClientConfig{tlsConfig}, nil
} else { } else {
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil return &STDECHClientConfig{
STDClientConfig: STDClientConfig{tlsConfig},
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
}, nil
} }
} }
@@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
type STDECHClientConfig struct { type STDECHClientConfig struct {
STDClientConfig STDClientConfig
dnsRouter adapter.DNSRouter access sync.Mutex
dnsRouter adapter.DNSRouter
lastTTL time.Duration
lastUpdate time.Time
} }
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 { tlsConn, err := s.fetchAndHandshake(ctx, conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
s.access.Lock()
defer s.access.Unlock()
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
message := &mDNS.Msg{ message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{ MsgHdr: mDNS.MsgHdr{
RecursionDesired: true, RecursionDesired: true,
@@ -123,6 +145,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
if response.Rcode != mDNS.RcodeSuccess { if response.Rcode != mDNS.RcodeSuccess {
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list") return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
} }
match:
for _, rr := range response.Answer { for _, rr := range response.Answer {
switch resource := rr.(type) { switch resource := rr.(type) {
case *mDNS.HTTPS: case *mDNS.HTTPS:
@@ -132,26 +155,23 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode ECH config") return nil, E.Cause(err, "decode ECH config")
} }
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
s.lastUpdate = time.Now()
s.config.EncryptedClientHelloConfigList = echConfigList s.config.EncryptedClientHelloConfigList = echConfigList
break match
} }
} }
} }
} }
return nil, E.New("no ECH config found in DNS records") if len(s.config.EncryptedClientHelloConfigList) == 0 {
return nil, E.New("no ECH config found in DNS records")
}
} }
tlsConn, err := s.Client(conn) return s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
} }
func (s *STDECHClientConfig) Clone() Config { func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter} return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
} }
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

View File

@@ -0,0 +1,5 @@
//go:build with_ech
package tls
var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration."

View File

@@ -8,10 +8,7 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"math/big" "math/big"
"net"
"time" "time"
M "github.com/sagernet/sing/common/metadata"
) )
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
@@ -38,30 +35,17 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
if err != nil { if err != nil {
return return
} }
var template *x509.Certificate template := &x509.Certificate{
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() { SerialNumber: serialNumber,
template = &x509.Certificate{ NotBefore: timeFunc().Add(time.Hour * -1),
SerialNumber: serialNumber, NotAfter: expire,
IPAddresses: []net.IP{serverAddress.AsSlice()}, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
NotBefore: timeFunc().Add(time.Hour * -1), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
NotAfter: expire, BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, Subject: pkix.Name{
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, CommonName: serverName,
BasicConstraintsValid: true, },
} DNSNames: []string{serverName},
} else {
template = &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
}
} }
if parent == nil { if parent == nil {
parent = template parent = template

View File

@@ -29,12 +29,13 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug" "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/ntp" "github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls" aTLS "github.com/sagernet/sing/common/tls"
utls "github.com/sagernet/utls"
utls "github.com/metacubex/utls"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, extension := range uConn.Extensions {
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
return curveID != utls.X25519MLKEM768
})
}
if ks, ok := extension.(*utls.KeyShareExtension); ok {
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
return share.Group != utls.X25519MLKEM768
})
}
}
err = uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
if len(uConfig.NextProtos) > 0 { if len(uConfig.NextProtos) > 0 {
for _, extension := range uConn.Extensions { for _, extension := range uConn.Extensions {
@@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil { if err != nil {
return nil, err return nil, err
} }
ecdheKey := uConn.HandshakeState.State13.EcdheKey keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
if keyShareKeys == nil {
return nil, E.New("nil KeyShareKeys")
}
ecdheKey := keyShareKeys.Ecdhe
if ecdheKey == nil { if ecdheKey == nil {
return nil, E.New("nil ecdhe_key") return nil, E.New("nil ecdheKey")
} }
authKey, err := ecdheKey.ECDH(publicKey) authKey, err := ecdheKey.ECDH(publicKey)
if err != nil { if err != nil {
@@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
response.Body.Close() response.Body.Close()
} }
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.uClient.config.SessionIDGenerator = generator
}
func (e *RealityClientConfig) Clone() Config { func (e *RealityClientConfig) Clone() Config {
return &RealityClientConfig{ return &RealityClientConfig{
e.ctx, e.ctx,

View File

@@ -1,4 +1,4 @@
//go:build with_reality_server //go:build with_utls
package tls package tls
@@ -7,28 +7,29 @@ import (
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt"
"net" "net"
"time" "time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
"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"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
utls "github.com/metacubex/utls"
) )
var _ ServerConfigCompat = (*RealityServerConfig)(nil) var _ ServerConfigCompat = (*RealityServerConfig)(nil)
type RealityServerConfig struct { type RealityServerConfig struct {
config *reality.Config config *utls.RealityConfig
} }
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) { func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
var tlsConfig reality.Config var tlsConfig utls.RealityConfig
if options.ACME != nil && len(options.ACME.Domain) > 0 { if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality") return nil, E.New("acme is unavailable in reality")
@@ -74,6 +75,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
} }
tlsConfig.SessionTicketsDisabled = true tlsConfig.SessionTicketsDisabled = true
tlsConfig.Log = func(format string, v ...any) {
if logger != nil {
logger.Trace(fmt.Sprintf(format, v...))
}
}
tlsConfig.Type = N.NetworkTCP tlsConfig.Type = N.NetworkTCP
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String() tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
@@ -89,16 +95,20 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference) tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool) tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range options.Reality.ShortID { if len(options.Reality.ShortID) == 0 {
var shortID [8]byte tlsConfig.ShortIds[[8]byte{0}] = true
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) } else {
if err != nil { for i, shortIDString := range options.Reality.ShortID {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
} }
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
} }
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain()) handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
@@ -109,10 +119,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
} }
if debug.Enabled {
tlsConfig.Show = true
}
return &RealityServerConfig{&tlsConfig}, nil return &RealityServerConfig{&tlsConfig}, nil
} }
@@ -153,7 +159,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
} }
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) { func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
tlsConn, err := reality.Server(ctx, conn, c.config) tlsConn, err := utls.RealityServer(ctx, conn, c.config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -169,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config {
var _ Conn = (*realityConnWrapper)(nil) var _ Conn = (*realityConnWrapper)(nil)
type realityConnWrapper struct { type realityConnWrapper struct {
*reality.Conn *utls.Conn
} }
func (c *realityConnWrapper) ConnectionState() ConnectionState { func (c *realityConnWrapper) ConnectionState() ConnectionState {

View File

@@ -1,15 +1,5 @@
//go:build !with_reality_server //go:build with_reality_server
package tls package tls
import ( var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration."
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
}

View File

@@ -6,6 +6,7 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"time"
"github.com/sagernet/fswatch" "github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -233,8 +234,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
key = content key = content
} }
if certificate == nil && key == nil && options.Insecure { if certificate == nil && key == nil && options.Insecure {
timeFunc := ntp.TimeFuncFromContext(ctx)
if timeFunc == nil {
timeFunc = time.Now
}
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName) return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
} }
} else { } else {
if certificate == nil { if certificate == nil {

View File

@@ -16,8 +16,8 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
utls "github.com/sagernet/utls"
utls "github.com/metacubex/utls"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )

View File

@@ -5,6 +5,7 @@ package tls
import ( import (
"context" "context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
@@ -14,5 +15,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
} }
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`) return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
}
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
} }

View File

@@ -1,7 +1,9 @@
package tf package tf
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"math/rand" "math/rand"
"net" "net"
"strings" "strings"
@@ -17,17 +19,19 @@ type Conn struct {
tcpConn *net.TCPConn tcpConn *net.TCPConn
ctx context.Context ctx context.Context
firstPacketWritten bool firstPacketWritten bool
splitRecord bool
fallbackDelay time.Duration fallbackDelay time.Duration
} }
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) { func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn) tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
return &Conn{ return &Conn{
Conn: conn, Conn: conn,
tcpConn: tcpConn, tcpConn: tcpConn,
ctx: ctx, ctx: ctx,
splitRecord: splitRecord,
fallbackDelay: fallbackDelay, fallbackDelay: fallbackDelay,
}, nil }
} }
func (c *Conn) Write(b []byte) (n int, err error) { func (c *Conn) Write(b []byte) (n int, err error) {
@@ -37,10 +41,12 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}() }()
serverName := indexTLSServerName(b) serverName := indexTLSServerName(b)
if serverName != nil { if serverName != nil {
if c.tcpConn != nil { if !c.splitRecord {
err = c.tcpConn.SetNoDelay(true) if c.tcpConn != nil {
if err != nil { err = c.tcpConn.SetNoDelay(true)
return if err != nil {
return
}
} }
} }
splits := strings.Split(serverName.ServerName, ".") splits := strings.Split(serverName.ServerName, ".")
@@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
currentIndex++ currentIndex++
} }
} }
var buffer bytes.Buffer
for i := 0; i <= len(splitIndexes); i++ { for i := 0; i <= len(splitIndexes); i++ {
var payload []byte var payload []byte
if i == 0 { if i == 0 {
payload = b[:splitIndexes[i]] payload = b[:splitIndexes[i]]
if c.splitRecord {
payload = payload[recordLayerHeaderLen:]
}
} else if i == len(splitIndexes) { } else if i == len(splitIndexes) {
payload = b[splitIndexes[i-1]:] payload = b[splitIndexes[i-1]:]
} else { } else {
payload = b[splitIndexes[i-1]:splitIndexes[i]] payload = b[splitIndexes[i-1]:splitIndexes[i]]
} }
if c.tcpConn != nil && i != len(splitIndexes) { if c.splitRecord {
payloadLen := uint16(len(payload))
buffer.Write(b[:3])
binary.Write(&buffer, binary.BigEndian, payloadLen)
buffer.Write(payload)
} else if c.tcpConn != nil && i != len(splitIndexes) {
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay) err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
if err != nil { if err != nil {
return return
@@ -82,11 +97,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
} }
} }
} }
if c.tcpConn != nil { if c.splitRecord {
err = c.tcpConn.SetNoDelay(false) _, err = c.tcpConn.Write(buffer.Bytes())
if err != nil { if err != nil {
return return
} }
} else {
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)
if err != nil {
return
}
}
} }
return len(b), nil return len(b), nil
} }

View File

@@ -0,0 +1,32 @@
package tf_test
import (
"context"
"crypto/tls"
"net"
"testing"
tf "github.com/sagernet/sing-box/common/tlsfragment"
"github.com/stretchr/testify/require"
)
func TestTLSFragment(t *testing.T) {
t.Parallel()
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
require.NoError(t, err)
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
}
func TestTLSRecordFragment(t *testing.T) {
t.Parallel()
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
require.NoError(t, err)
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
}

View File

@@ -1,7 +0,0 @@
package constant
const (
ScriptTypeSurge = "surge"
ScriptSourceTypeLocal = "local"
ScriptSourceTypeRemote = "remote"
)

View File

@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
if options.TraceBack != "" { if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack) debug.SetTraceback(options.TraceBack)
} }
if options.MemoryLimit != 0 { if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit) conntrack.MemoryLimit = options.MemoryLimit.Value()
} }
if options.OOMKiller != nil { if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -7,9 +7,9 @@ import (
"runtime/debug" "runtime/debug"
"strings" "strings"
"github.com/sagernet/sing-box/common/humanize"
"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/byteformats"
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"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
runtime.ReadMemStats(&memStats) runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject var memObject badjson.JSONObject
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse)) memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse)) memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased)) memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine()) memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS()) memObject.Put("rss", rusageMaxRSS())

View File

@@ -105,7 +105,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
} }
question := message.Question[0] question := message.Question[0]
if options.ClientSubnet.IsValid() { if options.ClientSubnet.IsValid() {
message = SetClientSubnet(message, options.ClientSubnet, true) message = SetClientSubnet(message, options.ClientSubnet)
} }
isSimpleRequest := len(message.Question) == 1 && isSimpleRequest := len(message.Question) == 1 &&
len(message.Ns) == 0 && len(message.Ns) == 0 &&
@@ -232,10 +232,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
record.Header().Ttl = timeToLive record.Header().Ttl = timeToLive
} }
} }
response.Id = messageId
if !disableCache { if !disableCache {
c.storeCache(transport, question, response, timeToLive) c.storeCache(transport, question, response, timeToLive)
} }
response.Id = messageId
requestEDNSOpt := message.IsEdns0()
responseEDNSOpt := response.IsEdns0()
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
return it.Header().Rrtype != dns.TypeOPT
})
if requestEDNSOpt != nil {
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
}
}
logExchangedResponse(c.logger, ctx, response, timeToLive) logExchangedResponse(c.logger, ctx, response, timeToLive)
return response, err return response, err
} }
@@ -483,7 +493,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
} }
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) { func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { if response.Rcode != dns.RcodeSuccess {
return nil, RcodeError(response.Rcode) return nil, RcodeError(response.Rcode)
} }
addresses := make([]netip.Addr, 0, len(response.Answer)) addresses := make([]netip.Addr, 0, len(response.Answer))

View File

@@ -15,6 +15,8 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
} }
responseLen := response.Len() responseLen := response.Len()
if responseLen > maxLen { if responseLen > maxLen {
copyResponse := *response
response = &copyResponse
response.Truncate(maxLen) response.Truncate(maxLen)
} }
buffer := buf.NewSize(headroom*2 + 1 + responseLen) buffer := buf.NewSize(headroom*2 + 1 + responseLen)

View File

@@ -6,7 +6,11 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg { func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
return setClientSubnet(message, clientSubnet, true)
}
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
var ( var (
optRecord *dns.OPT optRecord *dns.OPT
subnetOption *dns.EDNS0_SUBNET subnetOption *dns.EDNS0_SUBNET
@@ -19,9 +23,6 @@ findExists:
var isEDNS0Subnet bool var isEDNS0Subnet bool
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET) subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
if isEDNS0Subnet { if isEDNS0Subnet {
if !override {
return message
}
break findExists break findExists
} }
} }
@@ -37,14 +38,14 @@ findExists:
}, },
} }
message.Extra = append(message.Extra, optRecord) message.Extra = append(message.Extra, optRecord)
} else { } else if clone {
message = message.Copy() return setClientSubnet(message.Copy(), clientSubnet, false)
} }
if subnetOption == nil { if subnetOption == nil {
subnetOption = new(dns.EDNS0_SUBNET) subnetOption = new(dns.EDNS0_SUBNET)
subnetOption.Code = dns.EDNS0SUBNET
optRecord.Option = append(optRecord.Option, subnetOption) optRecord.Option = append(optRecord.Option, subnetOption)
} }
subnetOption.Code = dns.EDNS0SUBNET
if clientSubnet.Addr().Is4() { if clientSubnet.Addr().Is4() {
subnetOption.Family = 1 subnetOption.Family = 1
} else { } else {

View File

@@ -323,6 +323,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
err error err error
) )
printResult := func() { printResult := func() {
if err == nil && len(responseAddrs) == 0 {
err = E.New("empty result")
}
if err != nil { if err != nil {
if errors.Is(err, ErrResponseRejectedCached) { if errors.Is(err, ErrResponseRejectedCached) {
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)") r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
@@ -331,15 +334,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
} else { } else {
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
} }
} else if len(responseAddrs) == 0 { }
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") if err != nil {
err = RcodeNameError err = E.Cause(err, "lookup ", domain)
} }
} }
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy) responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
if cached { if cached {
if len(responseAddrs) == 0 { if len(responseAddrs) == 0 {
return nil, RcodeNameError return nil, E.New("lookup ", domain, ": empty result (cached)")
} }
return responseAddrs, nil return responseAddrs, nil
} }

View File

@@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 443 serverAddr.Port = 443
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewHTTPSRaw( return NewHTTPSRaw(
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions), dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
logger, logger,

View File

@@ -3,6 +3,7 @@ package local
import ( import (
"context" "context"
"math/rand" "math/rand"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -35,6 +36,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
} }
return &Transport{ return &Transport{
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
ctx: ctx,
hosts: hosts.NewFile(hosts.DefaultPath), hosts: hosts.NewFile(hosts.DefaultPath),
dialer: transportDialer, dialer: transportDialer,
}, nil }, nil
@@ -57,7 +59,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
} }
} }
systemConfig := getSystemDNSConfig() systemConfig := getSystemDNSConfig(t.ctx)
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, systemConfig, message, domain) return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
} else { } else {
@@ -89,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
startRacer := func(ctx context.Context, fqdn string) { startRacer := func(ctx context.Context, fqdn string) {
response, err := t.tryOneName(ctx, systemConfig, fqdn, message) response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
if err == nil { if err == nil {
addresses, _ := dns.MessageToAddresses(response) var addresses []netip.Addr
if len(addresses) == 0 { addresses, err = dns.MessageToAddresses(response)
if err == nil && len(addresses) == 0 {
err = E.New(fqdn, ": empty result") err = E.New(fqdn, ": empty result")
} }
} }

View File

@@ -1,6 +1,7 @@
package local package local
import ( import (
"context"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@@ -23,19 +24,21 @@ type resolverConfig struct {
var resolvConf resolverConfig var resolvConf resolverConfig
func getSystemDNSConfig() *dnsConfig { func getSystemDNSConfig(ctx context.Context) *dnsConfig {
resolvConf.tryUpdate("/etc/resolv.conf") resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
return resolvConf.dnsConfig.Load() return resolvConf.dnsConfig.Load()
} }
func (conf *resolverConfig) init() { func (conf *resolverConfig) init(ctx context.Context) {
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf")) conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
conf.lastChecked = time.Now() conf.lastChecked = time.Now()
conf.ch = make(chan struct{}, 1) conf.ch = make(chan struct{}, 1)
} }
func (conf *resolverConfig) tryUpdate(name string) { func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
conf.initOnce.Do(conf.init) conf.initOnce.Do(func() {
conf.init(ctx)
})
if conf.dnsConfig.Load().noReload { if conf.dnsConfig.Load().noReload {
return return
@@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
return return
} }
} }
dnsConf := dnsReadConfig(name) dnsConf := dnsReadConfig(ctx, name)
conf.dnsConfig.Store(dnsConf) conf.dnsConfig.Store(dnsConf)
} }

View File

@@ -11,6 +11,7 @@ package local
import "C" import "C"
import ( import (
"context"
"time" "time"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -18,7 +19,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func dnsReadConfig(_ string) *dnsConfig { func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
if C.res_init() != 0 { if C.res_init() != 0 {
return &dnsConfig{ return &dnsConfig{
servers: defaultNS, servers: defaultNS,

View File

@@ -4,6 +4,7 @@ package local
import ( import (
"bufio" "bufio"
"context"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -13,7 +14,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func dnsReadConfig(name string) *dnsConfig { func dnsReadConfig(_ context.Context, name string) *dnsConfig {
conf := &dnsConfig{ conf := &dnsConfig{
ndots: 1, ndots: 1,
timeout: 5 * time.Second, timeout: 5 * time.Second,

View File

@@ -1,6 +1,7 @@
package local package local
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -8,10 +9,13 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/service"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func dnsReadConfig(_ string) *dnsConfig { func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
conf := &dnsConfig{ conf := &dnsConfig{
ndots: 1, ndots: 1,
timeout: 5 * time.Second, timeout: 5 * time.Second,
@@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
conf.servers = defaultNS conf.servers = defaultNS
} }
}() }()
aas, err := adapterAddresses() addresses, err := adapterAddresses()
if err != nil { if err != nil {
return nil return nil
} }
var dnsAddresses []struct {
for _, aa := range aas { ifName string
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs. netip.Addr
if aa.OperStatus != windows.IfOperStatusUp { }
for _, address := range addresses {
if address.OperStatus != windows.IfOperStatusUp {
continue continue
} }
if address.IfType == windows.IF_TYPE_TUNNEL {
// Only take interfaces which have at least one gateway
if aa.FirstGatewayAddress == nil {
continue continue
} }
if address.FirstGatewayAddress == nil {
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { continue
sa, err := dns.Address.Sockaddr.Sockaddr() }
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
if err != nil { if err != nil {
continue continue
} }
var ip netip.Addr var dnsServerAddr netip.Addr
switch sa := sa.(type) { switch sockaddr := rawSockaddr.(type) {
case *syscall.SockaddrInet4: case *syscall.SockaddrInet4:
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]}) dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
case *syscall.SockaddrInet6: case *syscall.SockaddrInet6:
var addr16 [16]byte if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
copy(addr16[:], sa.Addr[:])
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
// fec0/10 IPv6 addresses are site local anycast DNS // fec0/10 IPv6 addresses are site local anycast DNS
// addresses Microsoft sets by default if no other // addresses Microsoft sets by default if no other
// IPv6 DNS address is set. Site local anycast is // IPv6 DNS address is set. Site local anycast is
@@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
// https://datatracker.ietf.org/doc/html/rfc3879 // https://datatracker.ietf.org/doc/html/rfc3879
continue continue
} }
ip = netip.AddrFrom16(addr16) dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
default: default:
// Unexpected type. // Unexpected type.
continue continue
} }
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53")) dnsAddresses = append(dnsAddresses, struct {
ifName string
netip.Addr
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
} }
} }
var myInterface string
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
myInterface = networkManager.InterfaceMonitor().MyInterface()
}
for _, address := range dnsAddresses {
if address.ifName == myInterface {
continue
}
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
}
return conf return conf
} }

View File

@@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 443 serverAddr.Port = 443
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &HTTP3Transport{ return &HTTP3Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions), TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
logger: logger, logger: logger,

View File

@@ -16,6 +16,7 @@ import (
sQUIC "github.com/sagernet/sing-quic" sQUIC "github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
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"
@@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &Transport{ return &Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
ctx: ctx, ctx: ctx,
@@ -140,12 +144,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer stream.Close()
defer stream.CancelRead(0)
err = transport.WriteMessage(stream, 0, message) err = transport.WriteMessage(stream, 0, message)
if err != nil { if err != nil {
stream.Close()
return nil, err return nil, err
} }
stream.Close()
return transport.ReadMessage(stream) return transport.ReadMessage(stream)
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
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"
@@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TCPTransport{ return &TCPTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options), TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
dialer: transportDialer, dialer: transportDialer,

View File

@@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TLSTransport{ return &TLSTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
logger: logger, logger: logger,

View File

@@ -13,6 +13,7 @@ import (
"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/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
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"
@@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
} }
@@ -117,7 +121,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
conn.access.Unlock() conn.access.Unlock()
defer func() { defer func() {
conn.access.Lock() conn.access.Lock()
delete(conn.callbacks, messageId) delete(conn.callbacks, exMessage.Id)
conn.access.Unlock() conn.access.Unlock()
}() }()
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
@@ -212,8 +216,8 @@ type dnsConnection struct {
func (c *dnsConnection) Close(err error) { func (c *dnsConnection) Close(err error) {
c.closeOnce.Do(func() { c.closeOnce.Do(func() {
close(c.done)
c.err = err c.err = err
close(c.done)
}) })
c.Conn.Close() c.Conn.Close()
} }

View File

@@ -20,9 +20,10 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
return dialer.NewDefaultOutbound(ctx), nil return dialer.NewDefaultOutbound(ctx), nil
} else { } else {
return dialer.NewWithOptions(dialer.Options{ return dialer.NewWithOptions(dialer.Options{
Context: ctx, Context: ctx,
Options: options.DialerOptions, Options: options.DialerOptions,
DirectResolver: true, DirectResolver: true,
LegacyDNSDialer: options.Legacy,
}) })
} }
} }
@@ -43,10 +44,11 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions)
return transportDialer, nil return transportDialer, nil
} else { } else {
return dialer.NewWithOptions(dialer.Options{ return dialer.NewWithOptions(dialer.Options{
Context: ctx, Context: ctx,
Options: options.DialerOptions, Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(), RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true, DirectResolver: true,
LegacyDNSDialer: options.Legacy,
}) })
} }
} }

View File

@@ -59,6 +59,9 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
transports := m.transports transports := m.transports
m.access.Unlock() m.access.Unlock()
if stage == adapter.StartStateStart { if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultTransport == nil {
return E.New("default DNS server not found: ", m.defaultTag)
}
return m.startTransports(m.transports) return m.startTransports(m.transports)
} else { } else {
for _, outbound := range transports { for _, outbound := range transports {

View File

@@ -2,10 +2,105 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-beta.13
* Add TLS record fragment route options **1**
* Add missing `accept_routes` option for Tailscale **2**
* Fixes and improvements
**1**:
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
**2**:
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
#### 1.12.0-beta.10
* Add control options for listeners **1**
* Fixes and improvements
**1**:
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
See [Listen Fields](/configuration/shared/listen/).
### 1.11.10
* Undeprecate the `block` outbound **1**
* Fixes and improvements
**1**:
Since we dont have a replacement for using the `block` outbound in selectors yet,
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.9
* Update quic-go to v0.51.0
* Fixes and improvements
### 1.11.9
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.5
* Fixes and improvements
### 1.11.8
* Improve `auto_redirect` **1**
* Fixes and improvements
**1**:
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.3
* Fixes and improvements
### 1.11.7
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.1 #### 1.12.0-beta.1
* Fixes and improvements * Fixes and improvements
**1**:
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
### 1.11.6
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-alpha.19
* Update gVisor to 20250319.0
* Fixes and improvements
#### 1.12.0-alpha.18 #### 1.12.0-alpha.18
* Add wildcard SNI support for ShadowTLS inbound **1** * Add wildcard SNI support for ShadowTLS inbound **1**
@@ -39,7 +134,8 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
* Fixes and improvements * Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ _We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-alpha.13 #### 1.12.0-alpha.13
@@ -110,7 +206,8 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
* Fixes and improvements * Fixes and improvements
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._ _This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
process._
#### 1.12.0-alpha.5 #### 1.12.0-alpha.5

View File

@@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
## Android ## Android
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
is used to provide per-application proxy features for VPN,
sing-box will not collect your app list.
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules, If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
sing-box uses the location permission in the background sing-box uses the location permission in the background
to get information about the connected Wi-Fi network to make them work. to get information about the connected Wi-Fi network to make them work.

View File

@@ -27,19 +27,20 @@ icon: material/alert-decagram
The type of the DNS server. The type of the DNS server.
| Type | Format | | Type | Format |
|-----------------|-----------------------------| |-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) | | empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) | | `local` | [Local](./local/) |
| `udp` | [UDP](./udp/) | | `hosts` | [Hosts](./hosts/) |
| `tls` | [TLS](./tls/) | | `tcp` | [TCP](./tcp/) |
| `https` | [HTTPS](./https/) | | `udp` | [UDP](./udp/) |
| `quic` | [QUIC](./quic/) | | `tls` | [TLS](./tls/) |
| `h3` | [HTTP/3](./http3/) | | `quic` | [QUIC](./quic/) |
| `predefined` | [Predefined](./predefined/) | | `https` | [HTTPS](./https/) |
| `dhcp` | [DHCP](./dhcp/) | | `h3` | [HTTP/3](./http3/) |
| `fakeip` | [Fake IP](./fakeip/) | | `dhcp` | [DHCP](./dhcp/) |
| `tailscale` | [Tailscale](./tailscale/) | | `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag #### tag

View File

@@ -27,19 +27,20 @@ icon: material/alert-decagram
DNS 服务器的类型。 DNS 服务器的类型。
| 类型 | 格式 | | 类型 | 格式 |
|-----------------|-----------------------------| |-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) | | empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) | | `local` | [Local](./local/) |
| `udp` | [UDP](./udp/) | | `hosts` | [Hosts](./hosts/) |
| `tls` | [TLS](./tls/) | | `tcp` | [TCP](./tcp/) |
| `https` | [HTTPS](./https/) | | `udp` | [UDP](./udp/) |
| `quic` | [QUIC](./quic/) | | `tls` | [TLS](./tls/) |
| `h3` | [HTTP/3](./http3/) | | `quic` | [QUIC](./quic/) |
| `predefined` | [Predefined](./predefined/) | | `https` | [HTTPS](./https/) |
| `dhcp` | [DHCP](./dhcp/) | | `h3` | [HTTP/3](./http3/) |
| `fakeip` | [Fake IP](./fakeip/) | | `dhcp` | [DHCP](./dhcp/) |
| `tailscale` | [Tailscale](./tailscale/) | | `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag #### tag

View File

@@ -15,6 +15,7 @@ icon: material/new-box
"control_url": "", "control_url": "",
"ephemeral": false, "ephemeral": false,
"hostname": "", "hostname": "",
"accept_routes": false,
"exit_node": "", "exit_node": "",
"exit_node_allow_lan_access": false, "exit_node_allow_lan_access": false,
"advertise_routes": [], "advertise_routes": [],
@@ -62,6 +63,10 @@ System hostname is used by default.
Example: `localhost` Example: `localhost`
#### accept_routes
Indicates whether the node should accept routes advertised by other nodes.
#### exit_node #### exit_node
The exit node name or IP address to use. The exit node name or IP address to use.

View File

@@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
Default padding scheme: Default padding scheme:
``` ```json
stop=8 [
0=34-120 "stop=8",
1=100-400 "0=30-30",
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 "1=100-400",
3=500-1000 "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
4=500-1000 "3=9-9,500-1000",
5=500-1000 "4=500-1000",
6=500-1000 "5=500-1000",
7=500-1000 "6=500-1000",
"7=500-1000"
]
``` ```
#### tls #### tls

View File

@@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
默认填充方案: 默认填充方案:
``` ```json
stop=8 [
0=34-120 "stop=8",
1=100-400 "0=30-30",
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500 "1=100-400",
3=500-1000 "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
4=500-1000 "3=9-9,500-1000",
5=500-1000 "4=500-1000",
6=500-1000 "5=500-1000",
7=500-1000 "6=500-1000",
"7=500-1000"
]
``` ```
#### tls #### tls

View File

@@ -211,6 +211,10 @@ Set the default route to the Tun.
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`. By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
!!! note "Also enable `auto_redirect`"
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
#### iproute2_table_index #### iproute2_table_index
!!! question "Since sing-box 1.10.0" !!! question "Since sing-box 1.10.0"
@@ -235,22 +239,29 @@ Linux iproute2 rule start index generated by `auto_route`.
Only supported on Linux with `auto_route` enabled. Only supported on Linux with `auto_route` enabled.
Automatically configure iptables/nftables to redirect connections. Improve TUN routing and performance using nftables.
*In Android* `auto_redirect` is always recommended on Linux, it provides better routing,
higher performance (better than tproxy),
and avoids conflicts between TUN and Docker bridge networks.
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater, Note that `auto_redirect` also works on Android,
but due to the lack of `nftables` and `ip6tables`,
only simple IPv4 TCP forwarding is performed.
To share your VPN connection over hotspot or repeater on Android,
use [VPNHotspot](https://github.com/Mygod/VPNHotspot). use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
*In Linux*: `auto_redirect` also automatically inserts compatibility rules
into the OpenWrt fw4 table, i.e.
it will work on routers without any extra configuration.
`auto_route` with `auto_redirect` works as expected on routers **without intervention**. Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
#### auto_redirect_input_mark #### auto_redirect_input_mark
!!! question "Since sing-box 1.10.0" !!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. Connection input mark used by `auto_redirect`.
`0x2023` is used by default. `0x2023` is used by default.
@@ -258,7 +269,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`
!!! question "Since sing-box 1.10.0" !!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`. Connection output mark used by `auto_redirect`.
`0x2024` is used by default. `0x2024` is used by default.
@@ -269,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*: *In Linux*:
* Let unsupported network unreachable * Let unsupported network unreachable
* Make ICMP traffic route to tun instead of upstream interfaces * For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
* Route all connections to tun
It prevents IP address leaks and makes DNS hijacking work on Android.
*In Windows*: *In Windows*:
* Add firewall rules to prevent DNS leak caused by * Let unsupported network unreachable
* prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some applications (such as VirtualBox) from working properly in certain situations. It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
#### route_address #### route_address
@@ -368,8 +377,6 @@ Exclude custom routes when `auto_route` is enabled.
Add the destination IP CIDR rules in the specified rule-sets to the firewall. Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Matched traffic will bypass the sing-box routes. Matched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
=== "Without `auto_redirect` enabled" === "Without `auto_redirect` enabled"
!!! question "Since sing-box 1.11.0" !!! question "Since sing-box 1.11.0"

View File

@@ -215,6 +215,10 @@ tun 接口的 IPv6 前缀。
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn` VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
!!! note "也启用 `auto_redirect`"
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy 并避免 TUN 与 Docker 桥接网络冲突。
#### iproute2_table_index #### iproute2_table_index
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"
@@ -239,21 +243,22 @@ tun 接口的 IPv6 前缀。
仅支持 Linux且需要 `auto_route` 已启用。 仅支持 Linux且需要 `auto_route` 已启用。
自动配置 iptables/nftables 以重定向连接 通过使用 nftables 改善 TUN 路由和性能
*在 Android 中* 在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy并避免了 TUN 和 Docker 桥接网络之间的冲突。
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。 请注意,`auto_redirect` 也适用于 Android但由于缺少 `nftables``ip6tables`,仅执行简单的 IPv4 TCP 转发。
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*: `auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
带有 `auto_redirect ``auto_route` 可以在路由器上按预期工作,**无需干预** `route.default_mark``[dialOptions].routing_mark` 冲突
#### auto_redirect_input_mark #### auto_redirect_input_mark
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输入标记。 `auto_redirect` 使用的连接输入标记。
默认使用 `0x2023` 默认使用 `0x2023`
@@ -261,29 +266,25 @@ tun 接口的 IPv6 前缀。
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输出标记。 `auto_redirect` 使用的连接输出标记。
默认使用 `0x2024` 默认使用 `0x2024`
#### strict_route #### strict_route
启用 `auto_route` 时执行严格的路由规则 启用 `auto_route`,强制执行严格的路由规则
*在 Linux 中*: *在 Linux 中*
* 不支持的网络无法到达 * 使不支持的网络不可达。
* 使 ICMP 流量路由到 tun 而不是上游接口 * 出于历史遗留原因,当未启用 `strict_route``auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
* 将所有连接路由到 tun
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。 *在 Windows 中*
*在 Windows 中*: * 使不支持的网络不可达。
* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露
* 添加防火墙规则以阻止 Windows 它可能会使某些 Windows 应用程序(如 VirtualBox在某些情况下无法正常工作。
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
造成的 DNS 泄露
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### route_address #### route_address
@@ -342,8 +343,6 @@ tun 接口的 IPv6 前缀。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。 将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。 不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用" === "`auto_redirect` 未启用"
!!! question "自 sing-box 1.11.0 起" !!! question "自 sing-box 1.11.0 起"

View File

@@ -2,10 +2,6 @@
icon: material/delete-clock icon: material/delete-clock
--- ---
!!! failure "Deprecated in sing-box 1.11.0"
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
### Structure ### Structure
```json ```json

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