mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
426 Commits
dev-mitm-2
...
v1.13.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a04aff02c | ||
|
|
97e20090cb | ||
|
|
3bbb23ea3b | ||
|
|
7da7601903 | ||
|
|
eaee6bc493 | ||
|
|
b34469986b | ||
|
|
0a88fc6314 | ||
|
|
e984ad753b | ||
|
|
88bda5c306 | ||
|
|
3294a46a28 | ||
|
|
ce285f8d79 | ||
|
|
4e73574144 | ||
|
|
8881882326 | ||
|
|
b675ed2563 | ||
|
|
a44f8c7b5d | ||
|
|
003cf13898 | ||
|
|
2f906adfa1 | ||
|
|
bef0a2f240 | ||
|
|
e27a335ee0 | ||
|
|
a1694d4c7b | ||
|
|
48e5344cea | ||
|
|
6e59a76941 | ||
|
|
6a7264aa91 | ||
|
|
bc23473411 | ||
|
|
8171e792cc | ||
|
|
589e4e5bd7 | ||
|
|
7dd91362b5 | ||
|
|
0e79256f15 | ||
|
|
05169b09ad | ||
|
|
99c125d8f3 | ||
|
|
e473c64cd6 | ||
|
|
be7254c335 | ||
|
|
bf0e432340 | ||
|
|
d592e2d12a | ||
|
|
dd164d9150 | ||
|
|
84b277615c | ||
|
|
13e425d5c3 | ||
|
|
626ef0b427 | ||
|
|
277c643c3e | ||
|
|
7a4c70ede9 | ||
|
|
55df080e2a | ||
|
|
71253f800e | ||
|
|
30ef92ec7b | ||
|
|
5ce866cc8a | ||
|
|
eca6a5da18 | ||
|
|
37a43dd63a | ||
|
|
2f377b2cdf | ||
|
|
fac4068214 | ||
|
|
ee07065f7b | ||
|
|
f70867e0a9 | ||
|
|
bf43a6655e | ||
|
|
bf055b8ae2 | ||
|
|
f88d249f03 | ||
|
|
10d6d22b73 | ||
|
|
65e7649952 | ||
|
|
d01534aa5c | ||
|
|
3efe0fdfdc | ||
|
|
67a0c19b07 | ||
|
|
3546a9368b | ||
|
|
8ab5c7695f | ||
|
|
07190d8d8a | ||
|
|
8e627088c6 | ||
|
|
f306f704bc | ||
|
|
537ca35cfe | ||
|
|
84a0f240f9 | ||
|
|
7f13a66e12 | ||
|
|
301e829266 | ||
|
|
b04310f285 | ||
|
|
8acef05e95 | ||
|
|
6922ec1070 | ||
|
|
9964bc39da | ||
|
|
644cd773c7 | ||
|
|
032e00f38d | ||
|
|
f9a9845901 | ||
|
|
b1fae028ce | ||
|
|
07d9ec4f68 | ||
|
|
61cecf0b01 | ||
|
|
6aa6ee2572 | ||
|
|
591665a302 | ||
|
|
1c2d38fcab | ||
|
|
1357294a63 | ||
|
|
a5135e33fd | ||
|
|
0f772f7bbe | ||
|
|
65f5f406b3 | ||
|
|
96f1f9e205 | ||
|
|
f56d9ab945 | ||
|
|
86fabd6a22 | ||
|
|
24a1e7cee4 | ||
|
|
223dd8bb1a | ||
|
|
68448de7d0 | ||
|
|
1ebff74c21 | ||
|
|
f0cd3422c1 | ||
|
|
e385a98ced | ||
|
|
670f32baee | ||
|
|
2747a00ba2 | ||
|
|
48e76038d0 | ||
|
|
6421252d44 | ||
|
|
216c4c8bd4 | ||
|
|
5841d410a1 | ||
|
|
63c8207d7a | ||
|
|
54ed58499d | ||
|
|
b1bdc18c85 | ||
|
|
a38030cc0b | ||
|
|
4626aa2cb0 | ||
|
|
5a40b673a4 | ||
|
|
541f63fee4 | ||
|
|
5de6f4a14f | ||
|
|
5658830077 | ||
|
|
0e50edc009 | ||
|
|
444f454810 | ||
|
|
d0e1fd6c7e | ||
|
|
17b4d1e010 | ||
|
|
06791470c9 | ||
|
|
ef14c8ca0e | ||
|
|
36dc883c7c | ||
|
|
6557bd7029 | ||
|
|
41b30c91d9 | ||
|
|
0f767d5ce1 | ||
|
|
328a6de797 | ||
|
|
886be6414d | ||
|
|
9362d3cab3 | ||
|
|
ced2e39dbf | ||
|
|
2159d8877b | ||
|
|
cb7dba3eff | ||
|
|
d9d7f7880d | ||
|
|
a031aaf2c0 | ||
|
|
4bca951773 | ||
|
|
140735dbde | ||
|
|
714a68bba1 | ||
|
|
573c6179ab | ||
|
|
510bf05e36 | ||
|
|
ae852e0be4 | ||
|
|
1955002ed8 | ||
|
|
44559fb7b9 | ||
|
|
0977c5cf73 | ||
|
|
07697bf931 | ||
|
|
5d1d1a1456 | ||
|
|
146383499e | ||
|
|
e81a76fdf9 | ||
|
|
de13137418 | ||
|
|
e42b818c2a | ||
|
|
fcde0c94e0 | ||
|
|
1af83e997d | ||
|
|
59ee7be72a | ||
|
|
c331ee3d5c | ||
|
|
36babe4bef | ||
|
|
c5f2cea802 | ||
|
|
8a200bf913 | ||
|
|
f16468e74f | ||
|
|
79c0b9f51d | ||
|
|
f98a3a4f65 | ||
|
|
b14cecaeb2 | ||
|
|
2594745ef8 | ||
|
|
cc3041322e | ||
|
|
f352f84483 | ||
|
|
cbf48e9b8c | ||
|
|
0ef7e8eca2 | ||
|
|
1a18e43a88 | ||
|
|
6849288d6d | ||
|
|
2edfed7d91 | ||
|
|
30c069f5b7 | ||
|
|
649163cb7b | ||
|
|
980e96250b | ||
|
|
963bc4b647 | ||
|
|
031f25c1c1 | ||
|
|
b40f642fa4 | ||
|
|
22782ca6fc | ||
|
|
1468d83895 | ||
|
|
97f0dc8a60 | ||
|
|
ee02532ab5 | ||
|
|
f1dd0dba78 | ||
|
|
f4ed684146 | ||
|
|
83f02d0bfb | ||
|
|
52fa5f20a3 | ||
|
|
f462ce5615 | ||
|
|
cef3e538ba | ||
|
|
acda4ce985 | ||
|
|
354ece2bdf | ||
|
|
de10bb00a9 | ||
|
|
fdc181106d | ||
|
|
8752b631bd | ||
|
|
378e39f70c | ||
|
|
043a2e7a07 | ||
|
|
7e190e92ca | ||
|
|
5eb318ba06 | ||
|
|
4a209f1afb | ||
|
|
c0ac3c748c | ||
|
|
a65d3e040a | ||
|
|
2358efe44a | ||
|
|
09d3b8f2c2 | ||
|
|
531de77124 | ||
|
|
44981fd803 | ||
|
|
4fb5ac292b | ||
|
|
0e23a3d7c2 | ||
|
|
76ee64ae50 | ||
|
|
e1dbcccab5 | ||
|
|
fba802effd | ||
|
|
9495b56772 | ||
|
|
a8434b176f | ||
|
|
ef0004400d | ||
|
|
0a63049845 | ||
|
|
2dcb86941f | ||
|
|
5c6eb89cfb | ||
|
|
5b92eeb3bf | ||
|
|
3518ce083b | ||
|
|
f13c54afc1 | ||
|
|
3388efe65a | ||
|
|
a11384b286 | ||
|
|
9dd9fb27cd | ||
|
|
0f2035149c | ||
|
|
cba364204a | ||
|
|
4e17788549 | ||
|
|
18a6719893 | ||
|
|
687343f6ca | ||
|
|
e061538c30 | ||
|
|
a6375c7530 | ||
|
|
45fa18a2e3 | ||
|
|
534cccce91 | ||
|
|
72dbcd3ad4 | ||
|
|
5533094984 | ||
|
|
ae2ecd6002 | ||
|
|
0098a2adc5 | ||
|
|
c0dd4a3f07 | ||
|
|
497ddb5829 | ||
|
|
811ff93549 | ||
|
|
96df69bcdc | ||
|
|
6cfa2b8b86 | ||
|
|
eea1e701b7 | ||
|
|
455e5de74d | ||
|
|
9533031891 | ||
|
|
80f8ea6849 | ||
|
|
50eadb00c7 | ||
|
|
d4012bd0b2 | ||
|
|
a902e9f9f6 | ||
|
|
da3ba573d8 | ||
|
|
bea9048cfe | ||
|
|
fc0f5ed83a | ||
|
|
c0588c30d7 | ||
|
|
24c940c51c | ||
|
|
407ee08d8a | ||
|
|
756585fb2a | ||
|
|
5662784afb | ||
|
|
3801901726 | ||
|
|
7d58174f1f | ||
|
|
d339f85087 | ||
|
|
b6a114f7f4 | ||
|
|
e586ef070e | ||
|
|
71a76e9ecb | ||
|
|
1d66474022 | ||
|
|
3934e53476 | ||
|
|
0146fbfc40 | ||
|
|
6ee3117755 | ||
|
|
e2440a569e | ||
|
|
7a1eee78df | ||
|
|
e3c8c0705f | ||
|
|
886d427337 | ||
|
|
d5432b4c27 | ||
|
|
42064fe7ec | ||
|
|
7cee76f9a6 | ||
|
|
ed5b2f2997 | ||
|
|
3b480de38a | ||
|
|
f990630ccc | ||
|
|
d33614d6a0 | ||
|
|
b3866bcea0 | ||
|
|
26ec73c71b | ||
|
|
c3403c5413 | ||
|
|
3b6ddcae37 | ||
|
|
dbdcce20a8 | ||
|
|
e7ef1b2368 | ||
|
|
ce32d1c2c3 | ||
|
|
596b66f397 | ||
|
|
d4fd43cf6f | ||
|
|
6c377f16e7 | ||
|
|
349db7baec | ||
|
|
1f3097da00 | ||
|
|
0b4b5e6f0f | ||
|
|
245273e6c1 | ||
|
|
54a0004de6 | ||
|
|
6a211f6ed6 | ||
|
|
aadb44ebd6 | ||
|
|
9b0db6ab15 | ||
|
|
5b363c347f | ||
|
|
cdea3f63d4 | ||
|
|
40a6260f6e | ||
|
|
a5e47f4e0f | ||
|
|
ac7bc587cb | ||
|
|
4e11a3585a | ||
|
|
63d3e9f6e5 | ||
|
|
d115e36ed8 | ||
|
|
af56b1a950 | ||
|
|
f9999a76fe | ||
|
|
42eb3841a1 | ||
|
|
fb622ccbdf | ||
|
|
d2dc3ddf72 | ||
|
|
e8499452f8 | ||
|
|
e0a6b31c03 | ||
|
|
7c923209ad | ||
|
|
bca2bd2fa1 | ||
|
|
fa99ca2757 | ||
|
|
7073f2a272 | ||
|
|
390e30ae7b | ||
|
|
23cf8c49e0 | ||
|
|
b17a024f6c | ||
|
|
1ed21085bb | ||
|
|
56409ff269 | ||
|
|
0c523980ff | ||
|
|
32873d06bc | ||
|
|
4accaccf77 | ||
|
|
ff416aacaf | ||
|
|
b97947e8ac | ||
|
|
dfcd9fb8c3 | ||
|
|
803811568e | ||
|
|
50b0bd5c39 | ||
|
|
2d02b2b1cf | ||
|
|
456fbecf16 | ||
|
|
668923c392 | ||
|
|
c51e9cbe06 | ||
|
|
60b451e6cf | ||
|
|
3e35390d8f | ||
|
|
f2dad289fb | ||
|
|
b4a8fa59f5 | ||
|
|
73de2a7d07 | ||
|
|
1699a7ce33 | ||
|
|
7743c6e881 | ||
|
|
9a5f69f435 | ||
|
|
5c4211e849 | ||
|
|
c1189e2a7b | ||
|
|
f18889369f | ||
|
|
91c7b638e8 | ||
|
|
6f793a0273 | ||
|
|
0f6c417c3c | ||
|
|
c830e9a634 | ||
|
|
e809623ec9 | ||
|
|
061276902b | ||
|
|
fa6f7d396e | ||
|
|
23666a9230 | ||
|
|
17576e9f66 | ||
|
|
90ec9c8bcb | ||
|
|
988ac62a1b | ||
|
|
3016338e34 | ||
|
|
bc35aca017 | ||
|
|
281d52a1ea | ||
|
|
b8502759b5 | ||
|
|
6f804adf39 | ||
|
|
36db31c55a | ||
|
|
4dbbf59c82 | ||
|
|
832eb4458d | ||
|
|
2cf989d306 | ||
|
|
7d3ee29bd0 | ||
|
|
cba0e46aba | ||
|
|
9b8ab3e61e | ||
|
|
47f18e823a | ||
|
|
2d1b824b62 | ||
|
|
d511698f3f | ||
|
|
cb435ea232 | ||
|
|
43a9016c83 | ||
|
|
255068fd40 | ||
|
|
098a00b025 | ||
|
|
dba0b5276b | ||
|
|
78ae935468 | ||
|
|
3ea5f76470 | ||
|
|
b4d294c05e | ||
|
|
83cf5f5c6a | ||
|
|
e7b3a8eebe | ||
|
|
ee3a42a67e | ||
|
|
50227c0f5f | ||
|
|
bc5eb1e1a5 | ||
|
|
995267a042 | ||
|
|
41226a6075 | ||
|
|
81d32181ce | ||
|
|
c5ecca3938 | ||
|
|
900888731c | ||
|
|
13e648e4b1 | ||
|
|
aff12ff671 | ||
|
|
101fb88255 | ||
|
|
8b489354e4 | ||
|
|
7dea6eb7a6 | ||
|
|
af1bfe4e3e | ||
|
|
d574e9eb52 | ||
|
|
2d7df1e1f2 | ||
|
|
1c0ffcf5b1 | ||
|
|
348cc39975 | ||
|
|
987899f94a | ||
|
|
d8b2d5142f | ||
|
|
134802d1ee | ||
|
|
e5e81b4de1 | ||
|
|
300c961efa | ||
|
|
7c7f512405 | ||
|
|
03e8d029c2 | ||
|
|
787b5f1931 | ||
|
|
56a7624618 | ||
|
|
3a84acf122 | ||
|
|
f600e02e47 | ||
|
|
e6d19de58a | ||
|
|
f2bbf6b2aa | ||
|
|
c54d50fd36 | ||
|
|
6a051054db | ||
|
|
49498f6439 | ||
|
|
144a890c71 | ||
|
|
afb4993445 | ||
|
|
4c9455b944 | ||
|
|
5fdc051a08 | ||
|
|
cb68a40c43 | ||
|
|
023218e6e7 | ||
|
|
2a24b94b8d | ||
|
|
c6531cf184 | ||
|
|
d4fa0ed349 | ||
|
|
10874d2dc4 | ||
|
|
5adaf1ac75 | ||
|
|
9668ea69b8 | ||
|
|
ae9bc7acf1 | ||
|
|
594ee480a2 | ||
|
|
a15b5a2463 | ||
|
|
991e755789 | ||
|
|
97d41ffde8 | ||
|
|
24af0766ac | ||
|
|
af17eaa537 | ||
|
|
3adc10a797 | ||
|
|
5eeef6b28e | ||
|
|
f4c29840c3 | ||
|
|
47fc3ebda4 | ||
|
|
9774a659b0 | ||
|
|
2e4a6de4e7 | ||
|
|
a530e424e9 | ||
|
|
0bfd487ee9 | ||
|
|
6aae834493 |
30
.fpm_openwrt
Normal file
30
.fpm_openwrt
Normal 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
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
-s dir
|
-s dir
|
||||||
--name sing-box
|
--name sing-box
|
||||||
--category net
|
--category net
|
||||||
--license GPLv3-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
|
--no-deb-generate-changes
|
||||||
|
--config-files /etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
release/completions/sing-box.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.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
1
.github/CRONET_GO_VERSION
vendored
Normal file
1
.github/CRONET_GO_VERSION
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1cc61ad20399081362ccbc18d650432d1a6d42ec
|
||||||
28
.github/deb2ipk.sh
vendored
Executable file
28
.github/deb2ipk.sh
vendored
Executable 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
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.23.6"
|
VERSION="1.25.5"
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
mkdir -p $HOME/go
|
||||||
cd $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 go_legacy
|
mv go go_win7
|
||||||
cd go_legacy
|
cd go_win7
|
||||||
|
|
||||||
# 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.25.x
|
||||||
# that means after golang1.24 release it must be changed
|
# that means after golang1.26 release it must be changed
|
||||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||||
# revert:
|
# revert:
|
||||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
|
||||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
|
||||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||||
13
.github/update_cronet.sh
vendored
Executable file
13
.github/update_cronet.sh
vendored
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
PROJECTS=$SCRIPT_DIR/../..
|
||||||
|
|
||||||
|
git -C $PROJECTS/cronet-go fetch origin main
|
||||||
|
git -C $PROJECTS/cronet-go fetch origin go
|
||||||
|
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
|
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
|
go mod tidy
|
||||||
|
git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
||||||
500
.github/workflows/build.yml
vendored
500
.github/workflows/build.yml
vendored
@@ -40,13 +40,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- 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.25.5
|
||||||
- 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: |-
|
||||||
@@ -68,64 +68,121 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ linux, windows, darwin, android ]
|
|
||||||
arch: [ "386", amd64, arm64 ]
|
|
||||||
legacy_go: [ false ]
|
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
- { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" }
|
||||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
- { 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: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
||||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
|
||||||
|
- { os: linux, arch: "386", go386: sse2, openwrt: "i386_pentium4" }
|
||||||
|
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
||||||
|
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
|
|
||||||
|
- { os: linux, arch: arm, goarm: "7", openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
|
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
||||||
|
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
|
|
||||||
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: "386", legacy_go: true }
|
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
||||||
- { os: windows, arch: amd64, legacy_go: true }
|
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
||||||
|
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android23" }
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" }
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android23" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: "386", ndk: "i686-linux-android23" }
|
||||||
exclude:
|
|
||||||
- { os: darwin, arch: "386" }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_go }}
|
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.25.5
|
||||||
- name: Cache Legacy Go
|
- name: Setup Go 1.24
|
||||||
if: matrix.require_legacy_go
|
if: matrix.legacy_go124
|
||||||
id: cache-legacy-go
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.24.10
|
||||||
|
- name: Cache Go for Windows 7
|
||||||
|
if: matrix.legacy_win7
|
||||||
|
id: cache-go-for-windows7
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go_win7
|
||||||
key: go_legacy_1236
|
key: go_win7_1255
|
||||||
- name: Setup Legacy Go
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_legacy_go.sh
|
.github/setup_go_for_windows7.sh
|
||||||
- name: Setup Legacy Go 2
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_go
|
if: matrix.legacy_win7
|
||||||
run: |-
|
run: |-
|
||||||
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
||||||
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == '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: Clone cronet-go
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
else
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain
|
||||||
|
fi
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
- 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"
|
||||||
@@ -133,24 +190,84 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
TAGS="${TAGS},with_ech"
|
TAGS="${TAGS},with_naive_outbound"
|
||||||
|
fi
|
||||||
|
if [[ "${{ matrix.variant }}" == "purego" ]]; then
|
||||||
|
TAGS="${TAGS},with_purego"
|
||||||
|
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
TAGS="${TAGS},with_musl"
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build (purego)
|
||||||
if: matrix.os != 'android'
|
if: matrix.variant == 'purego'
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
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 }}' \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
GOOS: ${{ matrix.os }}
|
GOOS: ${{ matrix.os }}
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract libcronet.so
|
||||||
|
if: matrix.variant == 'purego' && matrix.naive
|
||||||
|
run: |
|
||||||
|
cd ~/cronet-go
|
||||||
|
CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
|
||||||
|
- name: Build (glibc)
|
||||||
|
if: matrix.variant == 'glibc'
|
||||||
|
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 }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (musl)
|
||||||
|
if: matrix.variant == 'musl'
|
||||||
|
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 }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (non-variant)
|
||||||
|
if: matrix.os != 'android' && matrix.variant == ''
|
||||||
|
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 }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: ${{ matrix.os }}
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
@@ -161,7 +278,7 @@ jobs:
|
|||||||
export CXX="${CC}++"
|
export CXX="${CC}++"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
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 }}' \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -170,21 +287,36 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Set name
|
||||||
run: |-
|
run: |-
|
||||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
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 [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||||
|
fi
|
||||||
|
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-glibc"
|
||||||
|
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-musl"
|
||||||
|
fi
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
- name: Package DEB
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.deb" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
@@ -199,9 +331,10 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.rpm" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
@@ -217,32 +350,215 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
--architecture ${{ matrix.pacman }} \
|
--architecture ${{ matrix.pacman }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
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
|
||||||
|
SUFFIX=""
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
SUFFIX="_musl"
|
||||||
|
fi
|
||||||
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}${SUFFIX}.ipk"
|
||||||
|
done
|
||||||
|
rm "dist/openwrt.deb"
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
cd dist
|
cd dist
|
||||||
mkdir -p "${DIR_NAME}"
|
mkdir -p "${DIR_NAME}"
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
if [ '${{ matrix.os }}' = 'windoes' ]; then
|
if [ '${{ matrix.os }}' = 'windows' ]; then
|
||||||
cp sing-box.exe "${DIR_NAME}"
|
cp sing-box "${DIR_NAME}/sing-box.exe"
|
||||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
else
|
else
|
||||||
cp sing-box "${DIR_NAME}"
|
cp sing-box "${DIR_NAME}"
|
||||||
|
if [ -f libcronet.so ]; then
|
||||||
|
cp libcronet.so "${DIR_NAME}"
|
||||||
|
fi
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
fi
|
fi
|
||||||
rm -r "${DIR_NAME}"
|
rm -r "${DIR_NAME}"
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm -f dist/sing-box dist/libcronet.so
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
|
||||||
|
path: "dist"
|
||||||
|
build_darwin:
|
||||||
|
name: Build Darwin binaries
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { arch: amd64 }
|
||||||
|
- { arch: arm64 }
|
||||||
|
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
if: ${{ ! matrix.legacy_go124 }}
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.3
|
||||||
|
- name: Setup Go 1.24
|
||||||
|
if: matrix.legacy_go124
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.24.6
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
|
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then
|
||||||
|
TAGS="${TAGS},with_naive_outbound"
|
||||||
|
fi
|
||||||
|
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 }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: darwin
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Set name
|
||||||
|
run: |-
|
||||||
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}"
|
||||||
|
if [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||||
|
fi
|
||||||
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Archive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd dist
|
||||||
|
mkdir -p "${DIR_NAME}"
|
||||||
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
|
cp sing-box "${DIR_NAME}"
|
||||||
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
|
rm -r "${DIR_NAME}"
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: rm dist/sing-box
|
run: rm dist/sing-box
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
||||||
|
path: "dist"
|
||||||
|
build_windows:
|
||||||
|
name: Build Windows binaries
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { arch: amd64, naive: true }
|
||||||
|
- { arch: "386" }
|
||||||
|
- { arch: arm64, naive: true }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.4
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" `
|
||||||
|
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: windows
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
if: ${{ !matrix.naive }}
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" `
|
||||||
|
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: windows
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract libcronet.dll
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
|
||||||
|
$env:CGO_ENABLED = "0"
|
||||||
|
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
|
||||||
|
- name: Archive
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
||||||
|
mkdir "dist/$DIR_NAME"
|
||||||
|
Copy-Item LICENSE "dist/$DIR_NAME"
|
||||||
|
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
||||||
|
Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME"
|
||||||
|
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
||||||
|
Remove-Item -Recurse "dist/$DIR_NAME"
|
||||||
|
- name: Archive
|
||||||
|
if: ${{ !matrix.naive }}
|
||||||
|
run: |
|
||||||
|
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
||||||
|
mkdir "dist/$DIR_NAME"
|
||||||
|
Copy-Item LICENSE "dist/$DIR_NAME"
|
||||||
|
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
||||||
|
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
||||||
|
Remove-Item -Recurse "dist/$DIR_NAME"
|
||||||
|
- name: Cleanup
|
||||||
|
if: matrix.naive
|
||||||
|
run: Remove-Item dist/sing-box.exe, dist/libcronet.dll
|
||||||
|
- name: Cleanup
|
||||||
|
if: ${{ !matrix.naive }}
|
||||||
|
run: Remove-Item dist/sing-box.exe
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-windows_${{ matrix.arch }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@@ -252,14 +568,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- 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.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -307,18 +623,28 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp *.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
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
|
||||||
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/*.apk dist
|
||||||
|
cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist
|
||||||
|
VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)
|
||||||
|
VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2)
|
||||||
|
cat > dist/SFA-version-metadata.json << EOF
|
||||||
|
{
|
||||||
|
"version_code": ${VERSION_CODE},
|
||||||
|
"version_name": "${VERSION_NAME}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cat dist/SFA-version-metadata.json
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -332,14 +658,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- 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.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -380,7 +706,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/update_android_version --ci
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp *.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||||
./gradlew :app:publishPlayReleaseBundle
|
./gradlew :app:publishPlayReleaseBundle
|
||||||
@@ -391,7 +717,8 @@ jobs:
|
|||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-15
|
runs-on: macos-26
|
||||||
|
if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -429,7 +756,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -437,15 +764,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.25.5
|
||||||
- name: Setup Xcode stable
|
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
|
||||||
run: |-
|
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
|
||||||
- name: Setup Xcode beta
|
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
|
||||||
run: |-
|
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -514,10 +833,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 }}" \
|
||||||
@@ -561,14 +883,14 @@ jobs:
|
|||||||
--app-drop-link 0 0 \
|
--app-drop-link 0 0 \
|
||||||
--skip-jenkins \
|
--skip-jenkins \
|
||||||
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||||
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||||
cd "${{ matrix.archive }}"
|
cd "${{ matrix.archive }}"
|
||||||
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
|
||||||
@@ -577,16 +899,18 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
- build
|
- build
|
||||||
|
- build_darwin
|
||||||
|
- build_windows
|
||||||
- build_android
|
- build_android
|
||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
@@ -609,7 +933,7 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
153
.github/workflows/docker.yml
vendored
153
.github/workflows/docker.yml
vendored
@@ -1,6 +1,10 @@
|
|||||||
name: Publish Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - main-next
|
||||||
|
# - dev-next
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -13,8 +17,134 @@ env:
|
|||||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build_binary:
|
||||||
|
name: Build binary
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Naive-enabled builds (musl)
|
||||||
|
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
|
||||||
|
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
||||||
|
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
||||||
|
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
||||||
|
# Non-naive builds
|
||||||
|
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
|
||||||
|
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
|
||||||
|
- { arch: riscv64, docker_platform: "linux/riscv64" }
|
||||||
|
- { arch: s390x, docker_platform: "linux/s390x" }
|
||||||
|
steps:
|
||||||
|
- name: Get commit to build
|
||||||
|
id: ref
|
||||||
|
run: |-
|
||||||
|
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||||
|
ref="${{ github.ref_name }}"
|
||||||
|
else
|
||||||
|
ref="${{ github.event.inputs.tag }}"
|
||||||
|
fi
|
||||||
|
echo "ref=$ref"
|
||||||
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.4
|
||||||
|
- name: Clone cronet-go
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
VERSION=$(go run ./cmd/internal/read_tag)
|
||||||
|
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
||||||
|
- 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,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
|
TAGS="${TAGS},with_naive_outbound,with_musl"
|
||||||
|
fi
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build (naive)
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
- name: Build (non-naive)
|
||||||
|
if: ${{ ! matrix.naive }}
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
- name: Prepare artifact
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.docker_platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
# Rename binary to include arch info for Dockerfile.binary
|
||||||
|
BINARY_NAME="sing-box-${{ matrix.arch }}"
|
||||||
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
|
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
|
||||||
|
fi
|
||||||
|
mv sing-box "${BINARY_NAME}"
|
||||||
|
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
|
||||||
|
- name: Upload binary
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ env.BINARY_NAME }}
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
build_docker:
|
||||||
|
name: Build Docker image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build_binary
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
@@ -39,7 +169,7 @@ jobs:
|
|||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -47,6 +177,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
- name: Download binary
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
name: binary-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: .
|
||||||
|
- name: Prepare binary
|
||||||
|
run: |
|
||||||
|
# Find and make the binary executable
|
||||||
|
chmod +x sing-box-*
|
||||||
|
ls -la sing-box-*
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
@@ -68,8 +208,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
build-args: |
|
file: Dockerfile.binary
|
||||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
@@ -87,7 +226,7 @@ jobs:
|
|||||||
merge:
|
merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build
|
- build_docker
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -107,7 +246,7 @@ jobs:
|
|||||||
echo "latest=$latest"
|
echo "latest=$latest"
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
@@ -121,6 +260,7 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
|
if: github.event_name != 'push'
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
@@ -128,6 +268,7 @@ jobs:
|
|||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
|
|||||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -22,17 +22,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- 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.25
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: v2.4.0
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
verify: false
|
verify: false
|
||||||
|
|||||||
119
.github/workflows/linux.yml
vendored
119
.github/workflows/linux.yml
vendored
@@ -1,12 +1,21 @@
|
|||||||
name: Build Linux Packages
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - main-next
|
||||||
|
# - dev-next
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
forceBeta:
|
||||||
|
description: "Force beta"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -19,13 +28,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- 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.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -34,7 +43,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: |-
|
||||||
@@ -47,11 +56,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
# Naive-enabled builds (musl)
|
||||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
|
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
|
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
|
# Non-naive builds (unsupported architectures)
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { 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: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
@@ -60,19 +71,44 @@ jobs:
|
|||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- 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.25.5
|
||||||
- name: Setup Android NDK
|
- name: Clone cronet-go
|
||||||
if: matrix.os == 'android'
|
if: matrix.naive
|
||||||
uses: nttld/setup-ndk@v1
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
path: |
|
||||||
local-cache: true
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $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"
|
||||||
@@ -80,17 +116,32 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
TAGS="${TAGS},with_ech"
|
TAGS="${TAGS},with_naive_outbound,with_musl"
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build (naive)
|
||||||
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
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 }}' \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (non-naive)
|
||||||
|
if: ${{ ! matrix.naive }}
|
||||||
|
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 }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -102,24 +153,31 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Set name
|
- name: Set name
|
||||||
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
- name: Set beta name
|
- name: Set beta name
|
||||||
if: contains(needs.calculate_version.outputs.version, '-')
|
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
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
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/${NAME}
|
dist/sing-box=/usr/bin/sing-box
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
rm -rf $HOME/.gnupg
|
rm -rf $HOME/.gnupg
|
||||||
@@ -132,11 +190,13 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
-v "${{ needs.calculate_version.outputs.version }}" \
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/${NAME}
|
dist/sing-box=/usr/bin/sing-box
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
@@ -160,7 +220,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
@@ -169,12 +229,11 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Publish packages
|
- name: Publish packages
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |-
|
run: |-
|
||||||
wget -O fury-cli.deb https://github.com/gemfury/cli/releases/download/v0.23.0/fury-cli_0.23.0_linux_amd64.deb
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
sudo dpkg -i fury-cli.deb
|
|
||||||
fury migrate dist --as=sagernet --api-token ${{ secrets.FURY_TOKEN }}
|
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/vendor/
|
/vendor/
|
||||||
/*.json
|
/*.json
|
||||||
/*.js
|
|
||||||
/*.srs
|
/*.srs
|
||||||
/*.db
|
/*.db
|
||||||
/site/
|
/site/
|
||||||
@@ -16,4 +15,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
/venv/
|
/venv/
|
||||||
|
CLAUDE.md
|
||||||
|
AGENTS.md
|
||||||
|
/.claude/
|
||||||
|
|||||||
@@ -1,38 +1,59 @@
|
|||||||
linters:
|
version: "2"
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- gofumpt
|
|
||||||
- govet
|
|
||||||
- gci
|
|
||||||
- staticcheck
|
|
||||||
- paralleltest
|
|
||||||
- ineffassign
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
gci:
|
|
||||||
custom-order: true
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- all
|
|
||||||
- -SA1003
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.25"
|
||||||
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
|
linters:
|
||||||
|
default: none
|
||||||
issues:
|
enable:
|
||||||
exclude-dirs:
|
- govet
|
||||||
- transport/simple-obfs
|
- ineffassign
|
||||||
|
- paralleltest
|
||||||
|
- staticcheck
|
||||||
|
settings:
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -S1000
|
||||||
|
- -S1008
|
||||||
|
- -S1017
|
||||||
|
- -ST1003
|
||||||
|
- -QF1001
|
||||||
|
- -QF1003
|
||||||
|
- -QF1008
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
paths:
|
||||||
|
- transport/simple-obfs
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- prefix(github.com/sagernet/)
|
||||||
|
- default
|
||||||
|
custom-order: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- transport/simple-obfs
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
project_name: sing-box
|
|
||||||
builds:
|
|
||||||
- id: main
|
|
||||||
main: ./cmd/sing-box
|
|
||||||
flags:
|
|
||||||
- -v
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
|
||||||
- -s
|
|
||||||
- -buildid=
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
targets:
|
|
||||||
- linux_386
|
|
||||||
- linux_amd64_v1
|
|
||||||
- linux_arm64
|
|
||||||
- linux_arm_7
|
|
||||||
- linux_s390x
|
|
||||||
- linux_riscv64
|
|
||||||
- linux_mips64le
|
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
|
||||||
nfpms:
|
|
||||||
- &template
|
|
||||||
id: package
|
|
||||||
package_name: sing-box
|
|
||||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
|
||||||
builds:
|
|
||||||
- main
|
|
||||||
homepage: https://sing-box.sagernet.org/
|
|
||||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
|
||||||
description: The universal proxy platform.
|
|
||||||
license: GPLv3 or later
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
priority: extra
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
|
||||||
- src: release/config/sing-box@.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
- src: release/completions/sing-box.fish
|
|
||||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
- src: release/completions/sing-box.zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
- src: LICENSE
|
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
|
||||||
deb:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
fields:
|
|
||||||
Bugs: https://github.com/SagerNet/sing-box/issues
|
|
||||||
rpm:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
conflicts:
|
|
||||||
- sing-box-beta
|
|
||||||
- id: package_beta
|
|
||||||
<<: *template
|
|
||||||
package_name: sing-box-beta
|
|
||||||
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
conflicts:
|
|
||||||
- sing-box
|
|
||||||
release:
|
|
||||||
disable: true
|
|
||||||
furies:
|
|
||||||
- account: sagernet
|
|
||||||
ids:
|
|
||||||
- package
|
|
||||||
disable: "{{ not (not .Prerelease) }}"
|
|
||||||
- account: sagernet
|
|
||||||
ids:
|
|
||||||
- package_beta
|
|
||||||
disable: "{{ not .Prerelease }}"
|
|
||||||
211
.goreleaser.yaml
211
.goreleaser.yaml
@@ -1,211 +0,0 @@
|
|||||||
version: 2
|
|
||||||
project_name: sing-box
|
|
||||||
builds:
|
|
||||||
- &template
|
|
||||||
id: main
|
|
||||||
main: ./cmd/sing-box
|
|
||||||
flags:
|
|
||||||
- -v
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
|
||||||
- -s
|
|
||||||
- -buildid=
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
- with_script
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- GOTOOLCHAIN=local
|
|
||||||
targets:
|
|
||||||
- linux_386
|
|
||||||
- linux_amd64_v1
|
|
||||||
- linux_arm64
|
|
||||||
- linux_arm_6
|
|
||||||
- linux_arm_7
|
|
||||||
- linux_s390x
|
|
||||||
- linux_riscv64
|
|
||||||
- linux_mips64le
|
|
||||||
- windows_amd64_v1
|
|
||||||
- windows_386
|
|
||||||
- windows_arm64
|
|
||||||
- darwin_amd64_v1
|
|
||||||
- darwin_arm64
|
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
|
||||||
- id: legacy
|
|
||||||
<<: *template
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
- with_script
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
|
||||||
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
|
||||||
targets:
|
|
||||||
- windows_amd64_v1
|
|
||||||
- windows_386
|
|
||||||
- id: android
|
|
||||||
<<: *template
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=1
|
|
||||||
- GOTOOLCHAIN=local
|
|
||||||
overrides:
|
|
||||||
- goos: android
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
env:
|
|
||||||
- CC=armv7a-linux-androideabi21-clang
|
|
||||||
- CXX=armv7a-linux-androideabi21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: arm64
|
|
||||||
env:
|
|
||||||
- CC=aarch64-linux-android21-clang
|
|
||||||
- CXX=aarch64-linux-android21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: 386
|
|
||||||
env:
|
|
||||||
- CC=i686-linux-android21-clang
|
|
||||||
- CXX=i686-linux-android21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
env:
|
|
||||||
- CC=x86_64-linux-android21-clang
|
|
||||||
- CXX=x86_64-linux-android21-clang++
|
|
||||||
targets:
|
|
||||||
- android_arm_7
|
|
||||||
- android_arm64
|
|
||||||
- android_386
|
|
||||||
- android_amd64
|
|
||||||
archives:
|
|
||||||
- &template
|
|
||||||
id: archive
|
|
||||||
builds:
|
|
||||||
- main
|
|
||||||
- android
|
|
||||||
formats:
|
|
||||||
- tar.gz
|
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
|
||||||
formats:
|
|
||||||
- zip
|
|
||||||
wrap_in_directory: true
|
|
||||||
files:
|
|
||||||
- LICENSE
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
|
||||||
- id: archive-legacy
|
|
||||||
<<: *template
|
|
||||||
builds:
|
|
||||||
- legacy
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
|
||||||
nfpms:
|
|
||||||
- id: package
|
|
||||||
package_name: sing-box
|
|
||||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
|
||||||
builds:
|
|
||||||
- main
|
|
||||||
homepage: https://sing-box.sagernet.org/
|
|
||||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
|
||||||
description: The universal proxy platform.
|
|
||||||
license: GPLv3 or later
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
- archlinux
|
|
||||||
# - apk
|
|
||||||
# - ipk
|
|
||||||
priority: extra
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
|
||||||
- src: release/config/sing-box@.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
- src: release/completions/sing-box.fish
|
|
||||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
- src: release/completions/sing-box.zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
- src: LICENSE
|
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
|
||||||
deb:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
fields:
|
|
||||||
Bugs: https://github.com/SagerNet/sing-box/issues
|
|
||||||
rpm:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
overrides:
|
|
||||||
apk:
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/sing-box.initd
|
|
||||||
dst: /etc/init.d/sing-box
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
- src: release/completions/sing-box.fish
|
|
||||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
- src: release/completions/sing-box.zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
- src: LICENSE
|
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
|
||||||
ipk:
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/openwrt.init
|
|
||||||
dst: /etc/init.d/sing-box
|
|
||||||
- src: release/config/openwrt.conf
|
|
||||||
dst: /etc/config/sing-box
|
|
||||||
source:
|
|
||||||
enabled: false
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
|
||||||
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
|
|
||||||
checksum:
|
|
||||||
disable: true
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
|
|
||||||
signs:
|
|
||||||
- artifacts: checksum
|
|
||||||
release:
|
|
||||||
github:
|
|
||||||
owner: SagerNet
|
|
||||||
name: sing-box
|
|
||||||
draft: true
|
|
||||||
prerelease: auto
|
|
||||||
mode: replace
|
|
||||||
ids:
|
|
||||||
- archive
|
|
||||||
- package
|
|
||||||
skip_upload: true
|
|
||||||
partial:
|
|
||||||
by: target
|
|
||||||
10
Dockerfile
10
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
@@ -13,15 +13,13 @@ 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,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \
|
||||||
-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= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
||||||
&& apk add bash tzdata ca-certificates nftables \
|
|
||||||
&& rm -rf /var/cache/apk/*
|
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
8
Dockerfile.binary
Normal file
8
Dockerfile.binary
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM alpine
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETVARIANT
|
||||||
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
RUN set -ex \
|
||||||
|
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
||||||
|
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
|
||||||
|
ENTRYPOINT ["sing-box"]
|
||||||
48
Makefile
48
Makefile
@@ -1,14 +1,13 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale,with_script
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
|
||||||
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 github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
||||||
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)
|
||||||
|
|
||||||
@@ -18,13 +17,17 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
race:
|
||||||
|
export GOTOOLCHAIN=local && \
|
||||||
|
go build -race $(MAIN_PARAMS) $(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)
|
||||||
@@ -34,8 +37,11 @@ fmt:
|
|||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
|
fmt_docs:
|
||||||
|
go run ./cmd/internal/format_docs
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@v0.8.0
|
||||||
go install -v github.com/daixiang0/gci@latest
|
go install -v github.com/daixiang0/gci@latest
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@@ -46,7 +52,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -109,6 +115,16 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
|
export_ios_ipa:
|
||||||
|
cd ../sing-box-for-apple && \
|
||||||
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
||||||
|
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
||||||
|
|
||||||
|
upload_ios_ipa:
|
||||||
|
cd dist && \
|
||||||
|
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -176,6 +192,16 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
|
export_tvos_ipa:
|
||||||
|
cd ../sing-box-for-apple && \
|
||||||
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
||||||
|
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
||||||
|
|
||||||
|
upload_tvos_ipa:
|
||||||
|
cd dist && \
|
||||||
|
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -226,8 +252,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.10
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -246,4 +272,4 @@ clean:
|
|||||||
update:
|
update:
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents
|
||||||
|
|
||||||
|
<a href="https://go.warp.dev/sing-box">
|
||||||
|
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# sing-box
|
# sing-box
|
||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
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/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -25,17 +27,34 @@ type DNSClient interface {
|
|||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
|
||||||
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
DisableCache bool
|
LookupStrategy C.DomainStrategy
|
||||||
RewriteTTL *uint32
|
DisableCache bool
|
||||||
ClientSubnet netip.Prefix
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
|
if options == nil {
|
||||||
|
return &DNSQueryOptions{}, nil
|
||||||
|
}
|
||||||
|
transportManager := service.FromContext[DNSTransportManager](ctx)
|
||||||
|
transport, loaded := transportManager.Transport(options.Server)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
|
}
|
||||||
|
return &DNSQueryOptions{
|
||||||
|
Transport: transport,
|
||||||
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
|
DisableCache: options.DisableCache,
|
||||||
|
RewriteTTL: options.RewriteTTL,
|
||||||
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
@@ -49,6 +68,7 @@ type DNSTransport interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
|
Reset()
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||||
@@ -46,10 +48,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, endpoint := range m.endpoints {
|
for _, endpoint := range m.endpoints {
|
||||||
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,11 +72,15 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,11 +129,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(endpoint, stage)
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
|
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ type URLTestHistory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type URLTestHistoryStorage interface {
|
type URLTestHistoryStorage interface {
|
||||||
SetHook(hook chan<- struct{})
|
SetHook(hook *observable.Subscriber[struct{}])
|
||||||
LoadURLTestHistory(tag string) *URLTestHistory
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
DeleteURLTestHistory(tag string)
|
DeleteURLTestHistory(tag string)
|
||||||
StoreURLTestHistory(tag string, history *URLTestHistory)
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
@@ -52,10 +54,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 {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -55,13 +52,12 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
PacketSniffError error
|
SnifferNames []string
|
||||||
HTTPRequest *http.Request
|
SniffError error
|
||||||
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
|
||||||
@@ -88,7 +84,7 @@ type InboundContext struct {
|
|||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *ConnectionOwner
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
@@ -139,8 +135,7 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
|
|
||||||
func OverrideContext(ctx context.Context) context.Context {
|
func OverrideContext(ctx context.Context) context.Context {
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
var newMetadata InboundContext
|
newMetadata := *metadata
|
||||||
newMetadata = *metadata
|
|
||||||
return WithContext(ctx, &newMetadata)
|
return WithContext(ctx, &newMetadata)
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.InboundManager = (*Manager)(nil)
|
var _ adapter.InboundManager = (*Manager)(nil)
|
||||||
@@ -37,17 +39,22 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
for _, inbound := range m.inbounds {
|
inbounds := m.inbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,11 +71,15 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -120,11 +131,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(inbound, stage)
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SimpleLifecycle interface {
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -45,25 +56,47 @@ type LifecycleService interface {
|
|||||||
Lifecycle
|
Lifecycle
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(stage StartStage, services ...Lifecycle) error {
|
func getServiceName(service any) string {
|
||||||
for _, service := range services {
|
if named, ok := service.(interface {
|
||||||
if service == nil {
|
Type() string
|
||||||
continue
|
Tag() string
|
||||||
|
}); ok {
|
||||||
|
tag := named.Tag()
|
||||||
|
if tag != "" {
|
||||||
|
return named.Type() + "[" + tag + "]"
|
||||||
}
|
}
|
||||||
|
return named.Type()
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(service)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return strings.ToLower(t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
|
||||||
|
for _, service := range services {
|
||||||
|
name := getServiceName(service)
|
||||||
|
logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartNamed(stage StartStage, services []LifecycleService) error {
|
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
logger.Trace(stage, " ", service.Name())
|
||||||
|
startTime := time.Now()
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage.String(), " ", service.Name())
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
}
|
}
|
||||||
|
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
Service
|
SimpleLifecycle
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
Service: service,
|
SimpleLifecycle: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.Service, stage)
|
return LegacyStart(l.SimpleLifecycle, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.Service.Close()
|
return l.SimpleLifecycle.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type NetworkManager interface {
|
type NetworkManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
|
Initialize(ruleSets []RuleSet)
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
UpdateInterfaces() error
|
||||||
DefaultNetworkInterface() *NetworkInterface
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
@@ -20,12 +21,14 @@ type NetworkManager interface {
|
|||||||
DefaultOptions() NetworkOptions
|
DefaultOptions() NetworkOptions
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
AutoRedirectOutputMark() uint32
|
AutoRedirectOutputMark() uint32
|
||||||
|
AutoRedirectOutputMarkFunc() control.Func
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
NeedWIFIState() bool
|
||||||
WIFIState() WIFIState
|
WIFIState() WIFIState
|
||||||
ResetNetwork()
|
|
||||||
UpdateWIFIState()
|
UpdateWIFIState()
|
||||||
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"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-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +21,17 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundWithPreferredRoutes interface {
|
||||||
|
Outbound
|
||||||
|
PreferredDomain(domain string) bool
|
||||||
|
PreferredAddress(address netip.Addr) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DirectRouteOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ type Manager struct {
|
|||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultOutbound adapter.Outbound
|
defaultOutbound adapter.Outbound
|
||||||
defaultOutboundFallback adapter.Outbound
|
defaultOutboundFallback func() (adapter.Outbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||||
@@ -44,7 +46,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,23 +57,40 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
|
m.access.Unlock()
|
||||||
return E.New("default outbound not found: ", m.defaultTag)
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
m.defaultOutbound = defaultEndpoint
|
m.defaultOutbound = defaultEndpoint
|
||||||
}
|
}
|
||||||
|
if m.defaultOutbound == nil {
|
||||||
|
directOutbound, err := m.defaultOutboundFallback()
|
||||||
|
if err != nil {
|
||||||
|
m.access.Unlock()
|
||||||
|
return E.Cause(err, "create direct outbound for fallback")
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds, directOutbound)
|
||||||
|
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
||||||
|
m.defaultOutbound = directOutbound
|
||||||
|
}
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
} else {
|
} else {
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -96,22 +115,29 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
|||||||
}
|
}
|
||||||
started[outboundTag] = true
|
started[outboundTag] = true
|
||||||
canContinue = true
|
canContinue = true
|
||||||
|
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
|
||||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
m.logger.Trace("start ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("start ", name)
|
||||||
err := starter.Start(adapter.StartStateStart)
|
err := starter.Start(adapter.StartStateStart)
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
return E.Cause(err, "start ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
} else if starter, isStarter := outboundToStart.(interface {
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
Start() error
|
Start() error
|
||||||
}); isStarter {
|
}); isStarter {
|
||||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
m.logger.Trace("start ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("start ", name)
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
return E.Cause(err, "start ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(started) == len(outbounds) {
|
if len(started) == len(outbounds) {
|
||||||
@@ -158,11 +184,15 @@ func (m *Manager) Close() error {
|
|||||||
var err error
|
var err error
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
if closer, isCloser := outbound.(io.Closer); isCloser {
|
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||||
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -187,11 +217,7 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
if m.defaultOutbound != nil {
|
return m.defaultOutbound
|
||||||
return m.defaultOutbound
|
|
||||||
} else {
|
|
||||||
return m.defaultOutboundFallback
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
@@ -247,11 +273,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
|||||||
70
adapter/platform.go
Normal file
70
adapter/platform.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlatformInterface interface {
|
||||||
|
Initialize(networkManager NetworkManager) error
|
||||||
|
|
||||||
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
|
AutoDetectInterfaceControl(fd int) error
|
||||||
|
|
||||||
|
UsePlatformInterface() bool
|
||||||
|
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
|
|
||||||
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
|
|
||||||
|
UsePlatformNetworkInterfaces() bool
|
||||||
|
NetworkInterfaces() ([]NetworkInterface, error)
|
||||||
|
|
||||||
|
UnderNetworkExtension() bool
|
||||||
|
NetworkExtensionIncludeAllNetworks() bool
|
||||||
|
|
||||||
|
ClearDNSCache()
|
||||||
|
RequestPermissionForWIFIState() error
|
||||||
|
ReadWIFIState() WIFIState
|
||||||
|
SystemCertificates() []string
|
||||||
|
|
||||||
|
UsePlatformConnectionOwnerFinder() bool
|
||||||
|
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
|
||||||
|
|
||||||
|
UsePlatformWIFIMonitor() bool
|
||||||
|
|
||||||
|
UsePlatformNotification() bool
|
||||||
|
SendNotification(notification *Notification) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindConnectionOwnerRequest struct {
|
||||||
|
IpProtocol int32
|
||||||
|
SourceAddress string
|
||||||
|
SourcePort int32
|
||||||
|
DestinationAddress string
|
||||||
|
DestinationPort int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionOwner struct {
|
||||||
|
ProcessID uint32
|
||||||
|
UserId int32
|
||||||
|
UserName string
|
||||||
|
ProcessPath string
|
||||||
|
AndroidPackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Identifier string
|
||||||
|
TypeName string
|
||||||
|
TypeID int32
|
||||||
|
Title string
|
||||||
|
Subtitle string
|
||||||
|
Body string
|
||||||
|
OpenURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemProxyStatus struct {
|
||||||
|
Available bool
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
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"
|
||||||
@@ -19,12 +21,11 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext) error
|
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
SetTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
Service
|
SimpleLifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,27 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Lifecycle
|
||||||
Close() error
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceRegistry interface {
|
||||||
|
option.ServiceOptionsRegistry
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Services() []Service
|
||||||
|
Get(tag string) (Service, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
||||||
}
|
}
|
||||||
|
|||||||
21
adapter/service/adapter.go
Normal file
21
adapter/service/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
serviceType string
|
||||||
|
serviceTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
serviceType: serviceType,
|
||||||
|
serviceTag: serviceTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.serviceTag
|
||||||
|
}
|
||||||
158
adapter/service/manager.go
Normal file
158
adapter/service/manager.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.ServiceRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
services []adapter.Service
|
||||||
|
serviceByTag map[string]adapter.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
serviceByTag: make(map[string]adapter.Service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
services := m.services
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, service := range services {
|
||||||
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
err := adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " ", name)
|
||||||
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
services := m.services
|
||||||
|
m.services = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, service := range services {
|
||||||
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
|
err = E.Append(err, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", name)
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Services() []adapter.Service {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.services
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
return service, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.serviceByTag, tag)
|
||||||
|
index := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == service
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:index], m.services[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return service.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
||||||
|
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
err = adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " ", name)
|
||||||
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsService.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == existsService
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.services = append(m.services, service)
|
||||||
|
m.serviceByTag[tag] = service
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/service/registry.go
Normal file
72
adapter/service/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
18
adapter/ssm.go
Normal file
18
adapter/ssm.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedSSMServer interface {
|
||||||
|
Inbound
|
||||||
|
SetTracker(tracker SSMTracker)
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMTracker interface {
|
||||||
|
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
||||||
|
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
||||||
|
}
|
||||||
@@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func NewUpstreamContextHandlerEx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
_, myMetadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
_, myMetadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ type routeContextHandlerWrapperEx struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
metadata := ContextFrom(ctx)
|
_, metadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
metadata := ContextFrom(ctx)
|
_, metadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source,
|
Source: metadata.Source.Unwrap(),
|
||||||
Destination: metadata.Destination,
|
Destination: metadata.Destination.Unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
223
box.go
223
box.go
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -21,13 +22,10 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/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"
|
||||||
@@ -36,24 +34,23 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
dnsTransport *dns.TransportManager
|
service *boxService.Manager
|
||||||
dnsRouter *dns.Router
|
dnsTransport *dns.TransportManager
|
||||||
connection *route.ConnectionManager
|
dnsRouter *dns.Router
|
||||||
router *route.Router
|
connection *route.ConnectionManager
|
||||||
script *script.Manager
|
router *route.Router
|
||||||
mitm adapter.MITMEngine //*mitm.Engine
|
internalService []adapter.LifecycleService
|
||||||
services []adapter.LifecycleService
|
done chan struct{}
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -68,6 +65,7 @@ func Context(
|
|||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -88,6 +86,10 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
}
|
}
|
||||||
|
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -113,6 +116,12 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
|
if dnsTransportRegistry == nil {
|
||||||
|
return nil, E.New("missing DNS transport registry in context")
|
||||||
|
}
|
||||||
|
if serviceRegistry == nil {
|
||||||
|
return nil, E.New("missing service registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -129,7 +138,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
@@ -146,13 +155,19 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
|
||||||
var services []adapter.LifecycleService
|
var internalServices []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)
|
||||||
|
internalServices = append(internalServices, 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)
|
||||||
@@ -160,18 +175,20 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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 +196,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)
|
||||||
@@ -278,27 +295,41 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(common.Must1(
|
for i, serviceOptions := range options.Services {
|
||||||
direct.NewOutbound(
|
var tag string
|
||||||
|
if serviceOptions.Tag != "" {
|
||||||
|
tag = serviceOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = serviceManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
serviceOptions.Type,
|
||||||
|
serviceOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
|
return direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
),
|
)
|
||||||
))
|
})
|
||||||
dnsTransportManager.Initialize(common.Must1(
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
local.NewTransport(
|
return local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"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 {
|
||||||
@@ -308,7 +339,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
services = append(services, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@@ -317,9 +348,9 @@ 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)
|
internalServices = append(internalServices, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -327,8 +358,8 @@ 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)
|
internalServices = append(internalServices, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,34 +377,23 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
internalServices = append(internalServices, 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,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
dnsRouter: dnsRouter,
|
service: serviceManager,
|
||||||
connection: connectionManager,
|
dnsRouter: dnsRouter,
|
||||||
router: router,
|
connection: connectionManager,
|
||||||
script: scriptManager,
|
router: router,
|
||||||
mitm: mitmEngine,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
logFactory: logFactory,
|
||||||
logFactory: logFactory,
|
logger: logFactory.Logger(),
|
||||||
logger: logFactory.Logger(),
|
internalService: internalServices,
|
||||||
services: services,
|
done: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,15 +443,15 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
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(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
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(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -443,31 +463,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.inbound.Start(adapter.StartStateStart)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
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.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
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.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -481,17 +497,42 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
var err error
|
||||||
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
for _, closeItem := range []struct {
|
||||||
)
|
name string
|
||||||
for _, lifecycleService := range s.services {
|
service adapter.Lifecycle
|
||||||
|
}{
|
||||||
|
{"service", s.service},
|
||||||
|
{"endpoint", s.endpoint},
|
||||||
|
{"inbound", s.inbound},
|
||||||
|
{"outbound", s.outbound},
|
||||||
|
{"router", s.router},
|
||||||
|
{"connection", s.connection},
|
||||||
|
{"dns-router", s.dnsRouter},
|
||||||
|
{"dns-transport", s.dnsTransport},
|
||||||
|
{"network", s.network},
|
||||||
|
} {
|
||||||
|
s.logger.Trace("close ", closeItem.name)
|
||||||
|
startTime := time.Now()
|
||||||
|
err = E.Append(err, closeItem.service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", closeItem.name)
|
||||||
|
})
|
||||||
|
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
|
}
|
||||||
|
for _, lifecycleService := range s.internalService {
|
||||||
|
s.logger.Trace("close ", lifecycleService.Name())
|
||||||
|
startTime := time.Now()
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
|
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
|
s.logger.Trace("close logger")
|
||||||
|
startTime := time.Now()
|
||||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close logger")
|
return E.Cause(err, "close logger")
|
||||||
})
|
})
|
||||||
|
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,3 +551,7 @@ func (s *Box) Inbound() adapter.InboundManager {
|
|||||||
func (s *Box) Outbound() adapter.OutboundManager {
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
return s.outbound
|
return s.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) LogFactory() log.Factory {
|
||||||
|
return s.logFactory
|
||||||
|
}
|
||||||
|
|||||||
Submodule clients/android updated: aefe3c0290...fe128a6cd7
Submodule clients/apple updated: ae5818ee5a...532c140f05
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(10 * time.Minute)
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -134,6 +134,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
asc.PlatformTVOS,
|
asc.PlatformTVOS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
waitingForProcess := false
|
||||||
for _, platform := range platforms {
|
for _, platform := range platforms {
|
||||||
log.Info(string(platform), " list builds")
|
log.Info(string(platform), " list builds")
|
||||||
for {
|
for {
|
||||||
@@ -145,12 +146,13 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if *build.Attributes.ProcessingState != "VALID" {
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
|
waitingForProcess = true
|
||||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
@@ -177,7 +179,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
||||||
log.Info("waiting for process")
|
log.Info("waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
@@ -16,15 +17,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
|
withTailscale bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
|
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -44,8 +47,9 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
iosTags []string
|
darwinTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,18 +60,37 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
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= -checklinkname=0")
|
||||||
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+" -checklinkname=0")
|
||||||
|
|
||||||
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_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
darwinTags = append(darwinTags, "with_dhcp")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
type AndroidBuildConfig struct {
|
||||||
build_shared.FindSDK()
|
AndroidAPI int
|
||||||
|
OutputName string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTags(tags []string, exclude ...string) []string {
|
||||||
|
excludeMap := make(map[string]bool)
|
||||||
|
for _, tag := range exclude {
|
||||||
|
excludeMap[tag] = true
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, tag := range tags {
|
||||||
|
if !excludeMap[tag] {
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkJavaVersion() {
|
||||||
var javaPath string
|
var javaPath string
|
||||||
javaHome := os.Getenv("JAVA_HOME")
|
javaHome := os.Getenv("JAVA_HOME")
|
||||||
if javaHome == "" {
|
if javaHome == "" {
|
||||||
@@ -83,21 +106,24 @@ func buildAndroid() {
|
|||||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||||
log.Fatal("java version should be openjdk 17")
|
log.Fatal("java version should be openjdk 17")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var bindTarget string
|
func getAndroidBindTarget() string {
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
bindTarget = platform
|
return platform
|
||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
bindTarget = "android/arm64"
|
return "android/arm64"
|
||||||
} else {
|
|
||||||
bindTarget = "android"
|
|
||||||
}
|
}
|
||||||
|
return "android"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
|
"-o", config.OutputName,
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-androidapi", "21",
|
"-androidapi", strconv.Itoa(config.AndroidAPI),
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
@@ -108,34 +134,59 @@ func buildAndroid() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, memcTags...)
|
args = append(args, "-tags", strings.Join(config.Tags, ","))
|
||||||
if debugEnabled {
|
|
||||||
tags = append(tags, debugTags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "-tags", strings.Join(tags, ","))
|
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
command.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
err = command.Run()
|
err := command.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = "libbox.aar"
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||||
if rw.IsDir(copyPath) {
|
if rw.IsDir(copyPath) {
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
copyPath, _ = filepath.Abs(copyPath)
|
||||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Info("copied to ", copyPath)
|
log.Info("copied ", config.OutputName, " to ", copyPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildAndroid() {
|
||||||
|
build_shared.FindSDK()
|
||||||
|
checkJavaVersion()
|
||||||
|
|
||||||
|
bindTarget := getAndroidBindTarget()
|
||||||
|
|
||||||
|
// Build main variant (SDK 23)
|
||||||
|
mainTags := append([]string{}, sharedTags...)
|
||||||
|
mainTags = append(mainTags, memcTags...)
|
||||||
|
if debugEnabled {
|
||||||
|
mainTags = append(mainTags, debugTags...)
|
||||||
|
}
|
||||||
|
buildAndroidVariant(AndroidBuildConfig{
|
||||||
|
AndroidAPI: 23,
|
||||||
|
OutputName: "libbox.aar",
|
||||||
|
Tags: mainTags,
|
||||||
|
}, bindTarget)
|
||||||
|
|
||||||
|
// Build legacy variant (SDK 21, no naive outbound)
|
||||||
|
legacyTags := filterTags(sharedTags, "with_naive_outbound")
|
||||||
|
legacyTags = append(legacyTags, memcTags...)
|
||||||
|
if debugEnabled {
|
||||||
|
legacyTags = append(legacyTags, debugTags...)
|
||||||
|
}
|
||||||
|
buildAndroidVariant(AndroidBuildConfig{
|
||||||
|
AndroidAPI: 21,
|
||||||
|
OutputName: "libbox-legacy.aar",
|
||||||
|
Tags: legacyTags,
|
||||||
|
}, bindTarget)
|
||||||
|
}
|
||||||
|
|
||||||
func buildApple() {
|
func buildApple() {
|
||||||
var bindTarget string
|
var bindTarget string
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
@@ -151,7 +202,10 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-macos=" + strings.Join(memcTags, ","),
|
"-tags-not-macos=with_low_memory",
|
||||||
|
}
|
||||||
|
if !withTailscale {
|
||||||
|
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -160,7 +214,10 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
tags := append(sharedTags, darwinTags...)
|
||||||
|
if withTailscale {
|
||||||
|
tags = append(tags, memcTags...)
|
||||||
|
}
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
117
cmd/internal/format_docs/main.go
Normal file
117
cmd/internal/format_docs/main.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, ".md") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return processFile(path)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFile(path string) error {
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
modified := false
|
||||||
|
result := make([]string, 0, len(lines))
|
||||||
|
|
||||||
|
inQuoteBlock := false
|
||||||
|
materialLines := []int{} // indices of :material- lines in the block
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
// Check for quote block start
|
||||||
|
if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") {
|
||||||
|
inQuoteBlock = true
|
||||||
|
materialLines = nil
|
||||||
|
result = append(result, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside a quote block
|
||||||
|
if inQuoteBlock {
|
||||||
|
trimmed := strings.TrimPrefix(line, " ")
|
||||||
|
isMaterialLine := strings.HasPrefix(trimmed, ":material-")
|
||||||
|
isEmpty := strings.TrimSpace(line) == ""
|
||||||
|
isIndented := strings.HasPrefix(line, " ")
|
||||||
|
|
||||||
|
if isMaterialLine {
|
||||||
|
materialLines = append(materialLines, len(result))
|
||||||
|
result = append(result, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block ends when:
|
||||||
|
// - Empty line AFTER we've seen material lines, OR
|
||||||
|
// - Non-indented, non-empty line
|
||||||
|
blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)
|
||||||
|
if blockEnds {
|
||||||
|
// Process collected material lines
|
||||||
|
if len(materialLines) > 0 {
|
||||||
|
for j, idx := range materialLines {
|
||||||
|
isLast := j == len(materialLines)-1
|
||||||
|
resultLine := strings.TrimRight(result[idx], " ")
|
||||||
|
if !isLast {
|
||||||
|
// Add trailing two spaces for non-last lines
|
||||||
|
resultLine += " "
|
||||||
|
}
|
||||||
|
if result[idx] != resultLine {
|
||||||
|
modified = true
|
||||||
|
result[idx] = resultLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inQuoteBlock = false
|
||||||
|
materialLines = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where file ends while still in a block
|
||||||
|
if inQuoteBlock && len(materialLines) > 0 {
|
||||||
|
for j, idx := range materialLines {
|
||||||
|
isLast := j == len(materialLines)-1
|
||||||
|
resultLine := strings.TrimRight(result[idx], " ")
|
||||||
|
if !isLast {
|
||||||
|
resultLine += " "
|
||||||
|
}
|
||||||
|
if result[idx] != resultLine {
|
||||||
|
modified = true
|
||||||
|
result[idx] = resultLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if modified {
|
||||||
|
newContent := strings.Join(result, "\n")
|
||||||
|
if !bytes.Equal(content, []byte(newContent)) {
|
||||||
|
log.Info("formatted: ", path)
|
||||||
|
return os.WriteFile(path, []byte(newContent), 0o644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
284
cmd/internal/tun_bench/main.go
Normal file
284
cmd/internal/tun_bench/main.go
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
|
)
|
||||||
|
|
||||||
|
var iperf3Path string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := main0()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main0() error {
|
||||||
|
err := shell.Exec("sudo", "ls").Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results, err := runTests()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTests() ([]TestResult, error) {
|
||||||
|
boxPaths := []string{
|
||||||
|
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
||||||
|
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
||||||
|
"./sing-box",
|
||||||
|
}
|
||||||
|
stacks := []string{
|
||||||
|
"gvisor",
|
||||||
|
"system",
|
||||||
|
}
|
||||||
|
mtus := []int{
|
||||||
|
1500,
|
||||||
|
4064,
|
||||||
|
// 16384,
|
||||||
|
// 32768,
|
||||||
|
// 49152,
|
||||||
|
65535,
|
||||||
|
}
|
||||||
|
flagList := [][]string{
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
var results []TestResult
|
||||||
|
for _, boxPath := range boxPaths {
|
||||||
|
for _, stack := range stacks {
|
||||||
|
for _, mtu := range mtus {
|
||||||
|
if strings.HasPrefix(boxPath, ".") {
|
||||||
|
for _, flags := range flagList {
|
||||||
|
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, *result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, *result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestResult struct {
|
||||||
|
BoxPath string `json:"box_path"`
|
||||||
|
Stack string `json:"stack"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
Flags []string `json:"flags"`
|
||||||
|
MultiThread bool `json:"multi_thread"`
|
||||||
|
UploadSpeed string `json:"upload_speed"`
|
||||||
|
DownloadSpeed string `json:"download_speed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
||||||
|
testAddress := netip.MustParseAddr("1.1.1.1")
|
||||||
|
testConfig := option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeTun,
|
||||||
|
Options: &option.TunInboundOptions{
|
||||||
|
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
||||||
|
AutoRoute: true,
|
||||||
|
MTU: uint32(mtu),
|
||||||
|
Stack: stackName,
|
||||||
|
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
RawDefaultRule: option.RawDefaultRule{
|
||||||
|
IPCIDR: []string{testAddress.String()},
|
||||||
|
},
|
||||||
|
RuleAction: option.RuleAction{
|
||||||
|
Action: C.RuleActionTypeRouteOptions,
|
||||||
|
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
||||||
|
OverrideAddress: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AutoDetectInterface: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := include.Context(context.Background())
|
||||||
|
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tempConfig.Name())
|
||||||
|
encoder := json.NewEncoderContext(ctx, tempConfig)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(testConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "encode test config")
|
||||||
|
}
|
||||||
|
tempConfig.Close()
|
||||||
|
var sudoArgs []string
|
||||||
|
if len(flags) > 0 {
|
||||||
|
sudoArgs = append(sudoArgs, "env")
|
||||||
|
sudoArgs = append(sudoArgs, flags...)
|
||||||
|
}
|
||||||
|
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
||||||
|
boxProcess := shell.Exec("sudo", sudoArgs...)
|
||||||
|
boxProcess.Stdout = &stderrWriter{}
|
||||||
|
boxProcess.Stderr = io.Discard
|
||||||
|
err = boxProcess.Start()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if C.IsDarwin {
|
||||||
|
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
||||||
|
} else {
|
||||||
|
iperf3Path, err = exec.LookPath("iperf3")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverProcess := shell.Exec(iperf3Path, "-s")
|
||||||
|
serverProcess.Stdout = io.Discard
|
||||||
|
serverProcess.Stderr = io.Discard
|
||||||
|
err = serverProcess.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "start iperf3 server")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
args := []string{"-c", testAddress.String()}
|
||||||
|
if multiThread {
|
||||||
|
args = append(args, "-P", "10")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadProcess := shell.Exec(iperf3Path, args...)
|
||||||
|
output, err := uploadProcess.Read()
|
||||||
|
if err != nil {
|
||||||
|
boxProcess.Process.Signal(syscall.SIGKILL)
|
||||||
|
serverProcess.Process.Signal(syscall.SIGKILL)
|
||||||
|
println(output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
||||||
|
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
||||||
|
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
||||||
|
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
||||||
|
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
||||||
|
|
||||||
|
result = &TestResult{
|
||||||
|
BoxPath: boxPath,
|
||||||
|
Stack: stackName,
|
||||||
|
MTU: mtu,
|
||||||
|
Flags: flags,
|
||||||
|
MultiThread: multiThread,
|
||||||
|
UploadSpeed: uploadResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
||||||
|
output, err = downloadProcess.Read()
|
||||||
|
if err != nil {
|
||||||
|
boxProcess.Process.Signal(syscall.SIGKILL)
|
||||||
|
serverProcess.Process.Signal(syscall.SIGKILL)
|
||||||
|
println(output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
||||||
|
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
||||||
|
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
||||||
|
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
||||||
|
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
||||||
|
|
||||||
|
result.DownloadSpeed = downloadResult
|
||||||
|
|
||||||
|
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
||||||
|
if len(flags) > 0 {
|
||||||
|
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
||||||
|
}
|
||||||
|
if multiThread {
|
||||||
|
printArgs = append(printArgs, "(-P 10)")
|
||||||
|
}
|
||||||
|
fmt.Println(printArgs...)
|
||||||
|
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boxDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
boxProcess.Cmd.Wait()
|
||||||
|
close(boxDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
serverProcess.Process.Wait()
|
||||||
|
close(serverDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-boxDone:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
boxProcess.Process.Kill()
|
||||||
|
case <-time.After(4 * time.Second):
|
||||||
|
println("box process did not close!")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-serverDone:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
serverProcess.Process.Kill()
|
||||||
|
case <-time.After(4 * time.Second):
|
||||||
|
println("server process did not close!")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type stderrWriter struct{}
|
||||||
|
|
||||||
|
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
||||||
|
return os.Stderr.Write(p)
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
err = updateChromeIncludedRootCAs()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMozillaIncludedRootCAs() error {
|
func updateMozillaIncludedRootCAs() error {
|
||||||
@@ -69,3 +73,94 @@ func init() {
|
|||||||
generated.WriteString("}\n")
|
generated.WriteString("}\n")
|
||||||
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchChinaFingerprints() (map[string]bool, error) {
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countryIndex := slices.Index(header, "Country")
|
||||||
|
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
||||||
|
|
||||||
|
chinaFingerprints := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if record[countryIndex] == "China" {
|
||||||
|
chinaFingerprints[record[fingerprintIndex]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chinaFingerprints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChromeIncludedRootCAs() error {
|
||||||
|
chinaFingerprints, err := fetchChinaFingerprints()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
subjectIndex := slices.Index(header, "Subject")
|
||||||
|
statusIndex := slices.Index(header, "Google Chrome Status")
|
||||||
|
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
|
||||||
|
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
||||||
|
|
||||||
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var chromeIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
chromeIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record[statusIndex] != "Included" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if chinaFingerprints[record[fingerprintIndex]] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
generated.WriteString("\n // ")
|
||||||
|
generated.WriteString(record[subjectIndex])
|
||||||
|
generated.WriteString("\n")
|
||||||
|
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
cert := record[certIndex]
|
||||||
|
// Remove single quotes if present
|
||||||
|
if len(cert) > 0 && cert[0] == '\'' {
|
||||||
|
cert = cert[1 : len(cert)-1]
|
||||||
|
}
|
||||||
|
generated.WriteString(cert)
|
||||||
|
generated.WriteString("`))\n")
|
||||||
|
}
|
||||||
|
generated.WriteString("}\n")
|
||||||
|
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -68,6 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -69,7 +71,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@@ -78,3 +80,18 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
||||||
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion3
|
||||||
|
}
|
||||||
|
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion2
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
"github.com/sagernet/sing-box/common/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.Convert(reader)
|
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -50,6 +53,11 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return len(rule.AdGuardDomain) > 0
|
||||||
|
}) {
|
||||||
|
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
||||||
|
}
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -75,3 +83,19 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
if hasRule(rule.LogicalOptions.Rules, cond) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
73
cmd/sing-box/cmd_tools_connect.go
Normal file
73
cmd/sing-box/cmd_tools_connect.go
Normal 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
|
||||||
|
}
|
||||||
115
cmd/sing-box/cmd_tools_fetch.go
Normal file
115
cmd/sing-box/cmd_tools_fetch.go
Normal 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
|
||||||
|
}
|
||||||
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal 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.Conn, 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
|
||||||
|
}
|
||||||
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
176
common/badtls/raw_conn.go
Normal file
176
common/badtls/raw_conn.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//go:build go1.25 && badlinkname
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawConn struct {
|
||||||
|
pointer unsafe.Pointer
|
||||||
|
methods *Methods
|
||||||
|
|
||||||
|
IsClient *bool
|
||||||
|
IsHandshakeComplete *atomic.Bool
|
||||||
|
Vers *uint16
|
||||||
|
CipherSuite *uint16
|
||||||
|
|
||||||
|
RawInput *bytes.Buffer
|
||||||
|
Input *bytes.Reader
|
||||||
|
Hand *bytes.Buffer
|
||||||
|
|
||||||
|
CloseNotifySent *bool
|
||||||
|
CloseNotifyErr *error
|
||||||
|
|
||||||
|
In *RawHalfConn
|
||||||
|
Out *RawHalfConn
|
||||||
|
|
||||||
|
BytesSent *int64
|
||||||
|
PacketsSent *int64
|
||||||
|
|
||||||
|
ActiveCall *atomic.Int32
|
||||||
|
Tmp *[16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
|
||||||
|
var (
|
||||||
|
pointer unsafe.Pointer
|
||||||
|
methods *Methods
|
||||||
|
loaded bool
|
||||||
|
)
|
||||||
|
for _, tlsCreator := range methodRegistry {
|
||||||
|
pointer, methods, loaded = tlsCreator(rawTLSConn)
|
||||||
|
if loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &RawConn{
|
||||||
|
pointer: pointer,
|
||||||
|
methods: methods,
|
||||||
|
}
|
||||||
|
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
|
||||||
|
|
||||||
|
rawIsClient := rawConn.FieldByName("isClient")
|
||||||
|
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
|
||||||
|
return nil, E.New("invalid Conn.isClient")
|
||||||
|
}
|
||||||
|
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
||||||
|
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.isHandshakeComplete")
|
||||||
|
}
|
||||||
|
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawVers := rawConn.FieldByName("vers")
|
||||||
|
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
|
||||||
|
return nil, E.New("invalid Conn.vers")
|
||||||
|
}
|
||||||
|
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawCipherSuite := rawConn.FieldByName("cipherSuite")
|
||||||
|
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
|
||||||
|
return nil, E.New("invalid Conn.cipherSuite")
|
||||||
|
}
|
||||||
|
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawRawInput := rawConn.FieldByName("rawInput")
|
||||||
|
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.rawInput")
|
||||||
|
}
|
||||||
|
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawInput := rawConn.FieldByName("input")
|
||||||
|
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.input")
|
||||||
|
}
|
||||||
|
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawHand := rawConn.FieldByName("hand")
|
||||||
|
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.hand")
|
||||||
|
}
|
||||||
|
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
|
||||||
|
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
|
||||||
|
return nil, E.New("invalid Conn.closeNotifySent")
|
||||||
|
}
|
||||||
|
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
|
||||||
|
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("invalid Conn.closeNotifyErr")
|
||||||
|
}
|
||||||
|
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawIn := rawConn.FieldByName("in")
|
||||||
|
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.in")
|
||||||
|
}
|
||||||
|
halfIn, err := NewRawHalfConn(rawIn, methods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "invalid Conn.in")
|
||||||
|
}
|
||||||
|
conn.In = halfIn
|
||||||
|
|
||||||
|
rawOut := rawConn.FieldByName("out")
|
||||||
|
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.out")
|
||||||
|
}
|
||||||
|
halfOut, err := NewRawHalfConn(rawOut, methods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "invalid Conn.out")
|
||||||
|
}
|
||||||
|
conn.Out = halfOut
|
||||||
|
|
||||||
|
rawBytesSent := rawConn.FieldByName("bytesSent")
|
||||||
|
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
|
||||||
|
return nil, E.New("invalid Conn.bytesSent")
|
||||||
|
}
|
||||||
|
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawPacketsSent := rawConn.FieldByName("packetsSent")
|
||||||
|
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
|
||||||
|
return nil, E.New("invalid Conn.packetsSent")
|
||||||
|
}
|
||||||
|
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||||
|
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("invalid Conn.activeCall")
|
||||||
|
}
|
||||||
|
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawTmp := rawConn.FieldByName("tmp")
|
||||||
|
if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("invalid Conn.tmp")
|
||||||
|
}
|
||||||
|
conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr()))
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RawConn) ReadRecord() error {
|
||||||
|
return c.methods.readRecord(c.pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RawConn) HandlePostHandshakeMessage() error {
|
||||||
|
return c.methods.handlePostHandshakeMessage(c.pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
|
||||||
|
return c.methods.writeRecordLocked(c.pointer, typ, data)
|
||||||
|
}
|
||||||
121
common/badtls/raw_half_conn.go
Normal file
121
common/badtls/raw_half_conn.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
//go:build go1.25 && badlinkname
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawHalfConn struct {
|
||||||
|
pointer unsafe.Pointer
|
||||||
|
methods *Methods
|
||||||
|
*sync.Mutex
|
||||||
|
Err *error
|
||||||
|
Version *uint16
|
||||||
|
Cipher *any
|
||||||
|
Seq *[8]byte
|
||||||
|
ScratchBuf *[13]byte
|
||||||
|
TrafficSecret *[]byte
|
||||||
|
Mac *hash.Hash
|
||||||
|
RawKey *[]byte
|
||||||
|
RawIV *[]byte
|
||||||
|
RawMac *[]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
|
||||||
|
halfConn := &RawHalfConn{
|
||||||
|
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),
|
||||||
|
methods: methods,
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMutex := rawHalfConn.FieldByName("Mutex")
|
||||||
|
if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.Mutex")
|
||||||
|
}
|
||||||
|
halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawErr := rawHalfConn.FieldByName("err")
|
||||||
|
if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.err")
|
||||||
|
}
|
||||||
|
halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawVersion := rawHalfConn.FieldByName("version")
|
||||||
|
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.version")
|
||||||
|
}
|
||||||
|
halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawCipher := rawHalfConn.FieldByName("cipher")
|
||||||
|
if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.cipher")
|
||||||
|
}
|
||||||
|
halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawSeq := rawHalfConn.FieldByName("seq")
|
||||||
|
if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.seq")
|
||||||
|
}
|
||||||
|
halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawScratchBuf := rawHalfConn.FieldByName("scratchBuf")
|
||||||
|
if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.scratchBuf")
|
||||||
|
}
|
||||||
|
halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret")
|
||||||
|
if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.trafficSecret")
|
||||||
|
}
|
||||||
|
halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawMac := rawHalfConn.FieldByName("mac")
|
||||||
|
if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.mac")
|
||||||
|
}
|
||||||
|
halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawKey := rawHalfConn.FieldByName("rawKey")
|
||||||
|
if rawKey.IsValid() {
|
||||||
|
if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.rawKey")
|
||||||
|
}
|
||||||
|
halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawIV := rawHalfConn.FieldByName("rawIV")
|
||||||
|
if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.rawIV")
|
||||||
|
}
|
||||||
|
halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))
|
||||||
|
|
||||||
|
rawMAC := rawHalfConn.FieldByName("rawMac")
|
||||||
|
if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return nil, E.New("badtls: invalid halfConn.rawMac")
|
||||||
|
}
|
||||||
|
halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return halfConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {
|
||||||
|
return hc.methods.decrypt(hc.pointer, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *RawHalfConn) SetErrorLocked(err error) error {
|
||||||
|
return hc.methods.setErrorLocked(hc.pointer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {
|
||||||
|
hc.methods.setTrafficSecret(hc.pointer, suite, level, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *RawHalfConn) ExplicitNonceLen() int {
|
||||||
|
return hc.methods.explicitNonceLen(hc.pointer)
|
||||||
|
}
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
//go:build go1.21 && !without_badtls
|
//go:build go1.25 && badlinkname
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/tls"
|
"github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
@@ -21,63 +12,21 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil)
|
|||||||
|
|
||||||
type ReadWaitConn struct {
|
type ReadWaitConn struct {
|
||||||
tls.Conn
|
tls.Conn
|
||||||
halfAccess *sync.Mutex
|
rawConn *RawConn
|
||||||
rawInput *bytes.Buffer
|
readWaitOptions N.ReadWaitOptions
|
||||||
input *bytes.Reader
|
|
||||||
hand *bytes.Buffer
|
|
||||||
readWaitOptions N.ReadWaitOptions
|
|
||||||
tlsReadRecord func() error
|
|
||||||
tlsHandlePostHandshakeMessage func() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||||
var (
|
if _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn {
|
||||||
loaded bool
|
return conn, nil
|
||||||
tlsReadRecord func() error
|
|
||||||
tlsHandlePostHandshakeMessage func() error
|
|
||||||
)
|
|
||||||
for _, tlsCreator := range tlsRegistry {
|
|
||||||
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
|
||||||
if loaded {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !loaded {
|
rawConn, err := NewRawConn(conn)
|
||||||
return nil, os.ErrInvalid
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
|
||||||
rawHalfConn := rawConn.FieldByName("in")
|
|
||||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid half conn")
|
|
||||||
}
|
|
||||||
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
|
|
||||||
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid half mutex")
|
|
||||||
}
|
|
||||||
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
|
|
||||||
rawRawInput := rawConn.FieldByName("rawInput")
|
|
||||||
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid raw input")
|
|
||||||
}
|
|
||||||
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
|
||||||
rawInput0 := rawConn.FieldByName("input")
|
|
||||||
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid input")
|
|
||||||
}
|
|
||||||
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
|
|
||||||
rawHand := rawConn.FieldByName("hand")
|
|
||||||
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid hand")
|
|
||||||
}
|
|
||||||
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
|
||||||
return &ReadWaitConn{
|
return &ReadWaitConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
halfAccess: halfAccess,
|
rawConn: rawConn,
|
||||||
rawInput: rawInput,
|
|
||||||
input: input,
|
|
||||||
hand: hand,
|
|
||||||
tlsReadRecord: tlsReadRecord,
|
|
||||||
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,36 +36,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||||
err = c.HandshakeContext(context.Background())
|
//err = c.HandshakeContext(context.Background())
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
c.halfAccess.Lock()
|
c.rawConn.In.Lock()
|
||||||
defer c.halfAccess.Unlock()
|
defer c.rawConn.In.Unlock()
|
||||||
for c.input.Len() == 0 {
|
for c.rawConn.Input.Len() == 0 {
|
||||||
err = c.tlsReadRecord()
|
err = c.rawConn.ReadRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for c.hand.Len() > 0 {
|
for c.rawConn.Hand.Len() > 0 {
|
||||||
err = c.tlsHandlePostHandshakeMessage()
|
err = c.rawConn.HandlePostHandshakeMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer = c.readWaitOptions.NewBuffer()
|
buffer = c.readWaitOptions.NewBuffer()
|
||||||
n, err := c.input.Read(buffer.FreeBytes())
|
n, err := c.rawConn.Input.Read(buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
|
|
||||||
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&
|
||||||
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
// recordType(c.RawInput.Bytes()[0]) == recordTypeAlert {
|
||||||
c.rawInput.Bytes()[0] == 21 {
|
c.rawConn.RawInput.Bytes()[0] == 21 {
|
||||||
_ = c.tlsReadRecord()
|
_ = c.rawConn.ReadRecord()
|
||||||
// return n, err // will be io.EOF on closeNotify
|
// return n, err // will be io.EOF on closeNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,24 +77,6 @@ func (c *ReadWaitConn) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
func init() {
|
|
||||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
|
||||||
tlsConn, loaded := conn.(*tls.STDConn)
|
|
||||||
if !loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, func() error {
|
|
||||||
return stdTLSReadRecord(tlsConn)
|
|
||||||
}, func() error {
|
|
||||||
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
|
||||||
func stdTLSReadRecord(c *tls.STDConn) error
|
|
||||||
|
|
||||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.21 || without_badtls
|
//go:build !go1.25 || !badlinkname
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
//go:build go1.21 && !without_badtls && with_utls
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
_ "unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/utls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
|
||||||
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
|
||||||
if !loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, func() error {
|
|
||||||
return utlsReadRecord(tlsConn.Conn)
|
|
||||||
}, func() error {
|
|
||||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
|
||||||
func utlsReadRecord(c *tls.Conn) error
|
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
|
||||||
62
common/badtls/registry.go
Normal file
62
common/badtls/registry.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//go:build go1.25 && badlinkname
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Methods struct {
|
||||||
|
readRecord func(c unsafe.Pointer) error
|
||||||
|
handlePostHandshakeMessage func(c unsafe.Pointer) error
|
||||||
|
writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
||||||
|
|
||||||
|
setErrorLocked func(hc unsafe.Pointer, err error) error
|
||||||
|
decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
||||||
|
setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
||||||
|
explicitNonceLen func(hc unsafe.Pointer) int
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
||||||
|
tlsConn, loaded := conn.(*tls.Conn)
|
||||||
|
if !loaded {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
return unsafe.Pointer(tlsConn), &Methods{
|
||||||
|
readRecord: stdTLSReadRecord,
|
||||||
|
handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage,
|
||||||
|
writeRecordLocked: stdWriteRecordLocked,
|
||||||
|
|
||||||
|
setErrorLocked: stdSetErrorLocked,
|
||||||
|
decrypt: stdDecrypt,
|
||||||
|
setTrafficSecret: stdSetTrafficSecret,
|
||||||
|
explicitNonceLen: stdExplicitNonceLen,
|
||||||
|
}, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||||
|
func stdTLSReadRecord(c unsafe.Pointer) error
|
||||||
|
|
||||||
|
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error
|
||||||
|
|
||||||
|
//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked
|
||||||
|
func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
||||||
|
|
||||||
|
//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked
|
||||||
|
func stdSetErrorLocked(hc unsafe.Pointer, err error) error
|
||||||
|
|
||||||
|
//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt
|
||||||
|
func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
||||||
|
|
||||||
|
//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret
|
||||||
|
func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
||||||
|
|
||||||
|
//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen
|
||||||
|
func stdExplicitNonceLen(hc unsafe.Pointer) int
|
||||||
56
common/badtls/registry_utls.go
Normal file
56
common/badtls/registry_utls.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build go1.25 && badlinkname
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/metacubex/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
||||||
|
var pointer unsafe.Pointer
|
||||||
|
if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded {
|
||||||
|
pointer = unsafe.Pointer(uConn)
|
||||||
|
} else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded {
|
||||||
|
pointer = unsafe.Pointer(uConn.Conn)
|
||||||
|
} else {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
return pointer, &Methods{
|
||||||
|
readRecord: utlsReadRecord,
|
||||||
|
handlePostHandshakeMessage: utlsHandlePostHandshakeMessage,
|
||||||
|
writeRecordLocked: utlsWriteRecordLocked,
|
||||||
|
|
||||||
|
setErrorLocked: utlsSetErrorLocked,
|
||||||
|
decrypt: utlsDecrypt,
|
||||||
|
setTrafficSecret: utlsSetTrafficSecret,
|
||||||
|
explicitNonceLen: utlsExplicitNonceLen,
|
||||||
|
}, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||||
|
func utlsReadRecord(c unsafe.Pointer) error
|
||||||
|
|
||||||
|
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error
|
||||||
|
|
||||||
|
//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked
|
||||||
|
func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error)
|
||||||
|
|
||||||
|
//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked
|
||||||
|
func utlsSetErrorLocked(hc unsafe.Pointer, err error) error
|
||||||
|
|
||||||
|
//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt
|
||||||
|
func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
||||||
|
|
||||||
|
//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret
|
||||||
|
func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
||||||
|
|
||||||
|
//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen
|
||||||
|
func utlsExplicitNonceLen(hc unsafe.Pointer) int
|
||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
@@ -16,7 +18,19 @@ type Version struct {
|
|||||||
PreReleaseVersion int
|
PreReleaseVersion int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Version) After(anotherVersion Version) bool {
|
func (v Version) LessThan(anotherVersion Version) bool {
|
||||||
|
return !v.GreaterThanOrEqual(anotherVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) LessThanOrEqual(anotherVersion Version) bool {
|
||||||
|
return v == anotherVersion || anotherVersion.GreaterThan(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) GreaterThanOrEqual(anotherVersion Version) bool {
|
||||||
|
return v == anotherVersion || v.GreaterThan(anotherVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) GreaterThan(anotherVersion Version) bool {
|
||||||
if v.Major > anotherVersion.Major {
|
if v.Major > anotherVersion.Major {
|
||||||
return true
|
return true
|
||||||
} else if v.Major < anotherVersion.Major {
|
} else if v.Major < anotherVersion.Major {
|
||||||
@@ -44,19 +58,29 @@ func (v Version) After(anotherVersion Version) bool {
|
|||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
|
}
|
||||||
|
preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier)
|
||||||
|
anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier)
|
||||||
|
if preReleaseIdentifier < anotherPreReleaseIdentifier {
|
||||||
return true
|
return true
|
||||||
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
|
} else if preReleaseIdentifier > anotherPreReleaseIdentifier {
|
||||||
return false
|
|
||||||
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePreReleaseIdentifier(identifier string) int {
|
||||||
|
if strings.HasPrefix(identifier, "rc") {
|
||||||
|
return 1
|
||||||
|
} else if strings.HasPrefix(identifier, "beta") {
|
||||||
|
return 2
|
||||||
|
} else if strings.HasPrefix(identifier, "alpha") {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (v Version) VersionString() string {
|
func (v Version) VersionString() string {
|
||||||
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
||||||
}
|
}
|
||||||
@@ -83,6 +107,10 @@ func (v Version) BadString() string {
|
|||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsValid(versionName string) bool {
|
||||||
|
return semver.IsValid("v" + versionName)
|
||||||
|
}
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
func Parse(versionName string) (version Version) {
|
||||||
if strings.HasPrefix(versionName, "v") {
|
if strings.HasPrefix(versionName, "v") {
|
||||||
versionName = versionName[1:]
|
versionName = versionName[1:]
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1")))
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1")))
|
||||||
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1")))
|
||||||
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0")))
|
||||||
require.True(t, Parse("1.4").After(Parse("1.3")))
|
require.True(t, Parse("1.4").GreaterThan(Parse("1.3")))
|
||||||
}
|
}
|
||||||
|
|||||||
2817
common/certificate/chrome.go
Normal file
2817
common/certificate/chrome.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,36 +3,31 @@ package certificate
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"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/experimental/libbox/platform"
|
|
||||||
"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/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)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
|
access sync.RWMutex
|
||||||
systemPool *x509.CertPool
|
systemPool *x509.CertPool
|
||||||
currentPool *x509.CertPool
|
currentPool *x509.CertPool
|
||||||
certificate string
|
certificate string
|
||||||
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) {
|
||||||
@@ -40,7 +35,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
switch options.Store {
|
switch options.Store {
|
||||||
case C.CertificateStoreSystem, "":
|
case C.CertificateStoreSystem, "":
|
||||||
systemPool = x509.NewCertPool()
|
systemPool = x509.NewCertPool()
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
var systemValid bool
|
var systemValid bool
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
for _, cert := range platformInterface.SystemCertificates() {
|
for _, cert := range platformInterface.SystemCertificates() {
|
||||||
@@ -58,6 +53,8 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
}
|
}
|
||||||
case C.CertificateStoreMozilla:
|
case C.CertificateStoreMozilla:
|
||||||
systemPool = mozillaIncluded
|
systemPool = mozillaIncluded
|
||||||
|
case C.CertificateStoreChrome:
|
||||||
|
systemPool = chromeIncluded
|
||||||
case C.CertificateStoreNone:
|
case C.CertificateStoreNone:
|
||||||
systemPool = nil
|
systemPool = nil
|
||||||
default:
|
default:
|
||||||
@@ -96,19 +93,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +118,14 @@ func (s *Store) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Pool() *x509.CertPool {
|
func (s *Store) Pool() *x509.CertPool {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
return s.currentPool
|
return s.currentPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) update() error {
|
func (s *Store) update() error {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
var currentPool *x509.CertPool
|
var currentPool *x509.CertPool
|
||||||
if s.systemPool == nil {
|
if s.systemPool == nil {
|
||||||
currentPool = x509.NewCertPool()
|
currentPool = x509.NewCertPool()
|
||||||
@@ -202,15 +190,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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,10 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
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/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -36,7 +37,10 @@ func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
if ruleLine == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -92,7 +96,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,27 +124,35 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
|
isSuffix = true
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "##") {
|
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "#$#") {
|
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
||||||
|
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
||||||
|
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(ruleLine, "~") {
|
||||||
|
ignoredLines++
|
||||||
|
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -151,7 +163,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -159,13 +171,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
||||||
} else {
|
} else {
|
||||||
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -283,10 +295,112 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
if ignoredLines > 0 {
|
||||||
|
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
|
}
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
||||||
|
|
||||||
|
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
||||||
|
if len(rules) != 1 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
rule := rules[0]
|
||||||
|
var (
|
||||||
|
importantDomain []string
|
||||||
|
importantDomainRegex []string
|
||||||
|
importantExcludeDomain []string
|
||||||
|
importantExcludeDomainRegex []string
|
||||||
|
domain []string
|
||||||
|
domainRegex []string
|
||||||
|
excludeDomain []string
|
||||||
|
excludeDomainRegex []string
|
||||||
|
)
|
||||||
|
parse:
|
||||||
|
for {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
||||||
|
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
||||||
|
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||||
|
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||||
|
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||||
|
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||||
|
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
||||||
|
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||||
|
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||||
|
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
rule = rule.LogicalOptions.Rules[1]
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
domain = rule.DefaultOptions.AdGuardDomain
|
||||||
|
domainRegex = rule.DefaultOptions.DomainRegex
|
||||||
|
if len(domain)+len(domainRegex) == 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
break parse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var output bytes.Buffer
|
||||||
|
for _, ruleLine := range importantDomain {
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("$important\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range importantDomainRegex {
|
||||||
|
output.WriteString("/")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("/$important\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
for _, ruleLine := range importantExcludeDomain {
|
||||||
|
output.WriteString("@@")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("$important\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range importantExcludeDomainRegex {
|
||||||
|
output.WriteString("@@/")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("/$important\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range domain {
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range domainRegex {
|
||||||
|
output.WriteString("/")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("/\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range excludeDomain {
|
||||||
|
output.WriteString("@@")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("\n")
|
||||||
|
}
|
||||||
|
for _, ruleLine := range excludeDomainRegex {
|
||||||
|
output.WriteString("@@/")
|
||||||
|
output.WriteString(ruleLine)
|
||||||
|
output.WriteString("/\n")
|
||||||
|
}
|
||||||
|
return output.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -294,11 +408,9 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
||||||
return parseErr == nil
|
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -342,5 +454,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,13 +7,15 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := Convert(strings.NewReader(`
|
ruleString := `||sagernet.org^$important
|
||||||
|
@@|sing-box.sagernet.org^$important
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -21,10 +23,9 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org|
|
@@|sagernet.example.org^
|
||||||
||sagernet.org^$important
|
`
|
||||||
@@|sing-box.sagernet.org^$important
|
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
||||||
`))
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -75,15 +76,18 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
|
ruleFromOptions, err := FromOptions(rules)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ruleString, string(ruleFromOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := Convert(strings.NewReader(`
|
rules, err := ToOptions(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`))
|
`), logger.NOP())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -110,10 +114,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := Convert(strings.NewReader(`
|
rules, err := ToOptions(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`))
|
`), logger.NOP())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -12,15 +12,15 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/listener"
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"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/atomic"
|
|
||||||
"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"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
"github.com/database64128/tfo-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -29,8 +29,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tcpDialer
|
dialer4 tfo.Dialer
|
||||||
dialer6 tcpDialer
|
dialer6 tfo.Dialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
@@ -43,12 +43,12 @@ type DefaultDialer struct {
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback atomic.TypedValue[time.Time]
|
networkLastFallback common.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialer net.Dialer
|
dialer net.Dialer
|
||||||
@@ -66,23 +66,19 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
}
|
}
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
|
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||||
|
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
||||||
|
}
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
if !C.IsLinux {
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
return nil, E.New("`routing_mark` is only supported on Linux")
|
||||||
}
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
}
|
}
|
||||||
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 {
|
||||||
@@ -93,44 +89,47 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if !disableDefaultBind {
|
if defaultOptions.BindInterface != "" {
|
||||||
if defaultOptions.BindInterface != "" {
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
||||||
|
if platformInterface != nil {
|
||||||
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
|
networkStrategy = defaultOptions.NetworkStrategy
|
||||||
|
networkType = defaultOptions.NetworkType
|
||||||
|
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||||
|
}
|
||||||
|
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||||
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
|
}
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
|
bindFunc := networkManager.ProtectFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else {
|
||||||
|
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if networkManager.AutoDetectInterface() {
|
|
||||||
if platformInterface != nil {
|
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
|
||||||
networkStrategy = defaultOptions.NetworkStrategy
|
|
||||||
networkType = defaultOptions.NetworkType
|
|
||||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
|
||||||
}
|
|
||||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
|
||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
|
||||||
}
|
|
||||||
bindFunc := networkManager.ProtectFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
} else {
|
|
||||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 networkManager != nil {
|
||||||
|
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, markFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, markFunc)
|
||||||
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
@@ -143,9 +142,18 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
if !options.DisableTCPKeepAlive {
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
keepIdle := time.Duration(options.TCPKeepAlive)
|
||||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
if keepIdle == 0 {
|
||||||
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
}
|
||||||
|
keepInterval := time.Duration(options.TCPKeepAliveInterval)
|
||||||
|
if keepInterval == 0 {
|
||||||
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
|
}
|
||||||
|
dialer.KeepAlive = keepIdle
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
|
||||||
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
@@ -179,19 +187,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
}
|
}
|
||||||
if options.TCPMultiPath {
|
if options.TCPMultiPath {
|
||||||
if !go121Available {
|
dialer4.SetMultipathTCP(true)
|
||||||
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
|
||||||
}
|
|
||||||
setMultiPathTCP(&dialer4)
|
|
||||||
}
|
|
||||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}
|
||||||
|
tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
dialer4: tcpDialer4,
|
dialer4: tcpDialer4,
|
||||||
dialer6: tcpDialer6,
|
dialer6: tcpDialer6,
|
||||||
@@ -210,6 +209,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")
|
||||||
@@ -255,11 +270,11 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
}
|
}
|
||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
if N.NetworkName(network) == N.NetworkTCP {
|
if N.NetworkName(network) == N.NetworkTCP {
|
||||||
dialer = dialerFromTCPDialer(d.dialer4)
|
dialer = d.dialer4.Dialer
|
||||||
} else {
|
} else {
|
||||||
dialer = d.udpDialer4
|
dialer = d.udpDialer4
|
||||||
}
|
}
|
||||||
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||||
var (
|
var (
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
isPrimary bool
|
isPrimary bool
|
||||||
@@ -301,6 +316,14 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
||||||
|
if !destination.Is6() {
|
||||||
|
return d.dialer6.Dialer
|
||||||
|
} else {
|
||||||
|
return d.dialer4.Dialer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if strategy == nil {
|
if strategy == nil {
|
||||||
strategy = d.networkStrategy
|
strategy = d.networkStrategy
|
||||||
@@ -334,8 +357,8 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return trackPacketConn(packetConn, nil)
|
return trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) WireGuardControl() control.Func {
|
||||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
return d.udpListener.Control
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/metacubex/tfo-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpDialer = tfo.Dialer
|
|
||||||
|
|
||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|
||||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
|
||||||
return dialer.Dialer
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build go1.21
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
const go121Available = true
|
|
||||||
|
|
||||||
func setMultiPathTCP(dialer *net.Dialer) {
|
|
||||||
dialer.SetMultipathTCP(true)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//go:build !go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpDialer = net.Dialer
|
|
||||||
|
|
||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|
||||||
if tfoEnabled {
|
|
||||||
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
|
|
||||||
}
|
|
||||||
return dialer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
|
||||||
return dialer
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !go1.21
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const go121Available = false
|
|
||||||
|
|
||||||
func setMultiPathTCP(dialer *net.Dialer) {
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,7 @@ type Options struct {
|
|||||||
ResolverOnDetour bool
|
ResolverOnDetour bool
|
||||||
NewDialer bool
|
NewDialer bool
|
||||||
LegacyDNSDialer bool
|
LegacyDNSDialer bool
|
||||||
|
DirectOutbound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with NewWithOptions
|
// TODO: merge with NewWithOptions
|
||||||
@@ -82,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{
|
||||||
@@ -94,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 {
|
||||||
|
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(
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,15 +6,15 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
|
||||||
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/metacubex/tfo-go"
|
"github.com/database64128/tfo-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type slowOpenConn struct {
|
type slowOpenConn struct {
|
||||||
@@ -24,13 +22,15 @@ type slowOpenConn struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
network string
|
network string
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn atomic.Pointer[net.TCPConn]
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
|
done chan struct{}
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
|
closeOnce sync.Once
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
@@ -45,26 +45,30 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
select {
|
if conn != nil {
|
||||||
case <-c.create:
|
return conn.Read(b)
|
||||||
if c.err != nil {
|
}
|
||||||
return 0, c.err
|
select {
|
||||||
}
|
case <-c.create:
|
||||||
case <-c.ctx.Done():
|
if c.err != nil {
|
||||||
return 0, c.ctx.Err()
|
return 0, c.err
|
||||||
}
|
}
|
||||||
|
return c.conn.Load().Read(b)
|
||||||
|
case <-c.done:
|
||||||
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
if c.conn != nil {
|
tcpConn := c.conn.Load()
|
||||||
return c.conn.Write(b)
|
if tcpConn != nil {
|
||||||
|
return tcpConn.Write(b)
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -73,13 +77,16 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Load().Write(b)
|
||||||
|
case <-c.done:
|
||||||
|
return 0, os.ErrClosed
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.conn = nil
|
c.err = err
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
} else {
|
||||||
|
c.conn.Store(conn.(*net.TCPConn))
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -87,74 +94,87 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
return common.Close(c.conn)
|
c.closeOnce.Do(func() {
|
||||||
|
close(c.done)
|
||||||
|
conn := c.conn.Load()
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.LocalAddr()
|
return conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.RemoteAddr()
|
return conn.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetDeadline(t)
|
return conn.SetDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetReadDeadline(t)
|
return conn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetWriteDeadline(t)
|
return conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Upstream() any {
|
func (c *slowOpenConn) Upstream() any {
|
||||||
return c.conn
|
return common.PtrOrNil(c.conn.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) ReaderReplaceable() bool {
|
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||||
return c.conn != nil
|
return c.conn.Load() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriterReplaceable() bool {
|
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||||
return c.conn != nil
|
return c.conn.Load() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LazyHeadroom() bool {
|
func (c *slowOpenConn) LazyHeadroom() bool {
|
||||||
return c.conn == nil
|
return c.conn.Load() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
func (c *slowOpenConn) NeedHandshake() bool {
|
||||||
return c.conn == nil
|
return c.conn.Load() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.ctx.Done():
|
case <-c.done:
|
||||||
return 0, c.ctx.Err()
|
return 0, c.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn.Load())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
//go:build !go1.20
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
return dialer.DialContext(ctx, network, destination.String())
|
|
||||||
default:
|
|
||||||
return dialer.DialContext(ctx, network, destination.AddrString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WireGuardListener interface {
|
type WireGuardListener interface {
|
||||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
WireGuardControl() control.Func
|
||||||
}
|
}
|
||||||
|
|
||||||
var WgControlFns []control.Func
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
133
common/ktls/ktls.go
Normal file
133
common/ktls/ktls.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
aTLS.Conn
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
conn net.Conn
|
||||||
|
rawConn *badtls.RawConn
|
||||||
|
syscallConn syscall.Conn
|
||||||
|
rawSyscallConn syscall.RawConn
|
||||||
|
readWaitOptions N.ReadWaitOptions
|
||||||
|
kernelTx bool
|
||||||
|
kernelRx bool
|
||||||
|
pendingRxSplice bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
|
||||||
|
err := Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
syscallConn, isSyscallConn := N.CastReader[interface {
|
||||||
|
io.Reader
|
||||||
|
syscall.Conn
|
||||||
|
}](conn.NetConn())
|
||||||
|
if !isSyscallConn {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
rawSyscallConn, err := syscallConn.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawConn, err := badtls.NewRawConn(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if *rawConn.Vers != tls.VersionTLS13 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
for rawConn.RawInput.Len() > 0 {
|
||||||
|
err = rawConn.ReadRecord()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for rawConn.Hand.Len() > 0 {
|
||||||
|
err = rawConn.HandlePostHandshakeMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "handle post-handshake messages")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kConn := &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
conn: conn.NetConn(),
|
||||||
|
rawConn: rawConn,
|
||||||
|
syscallConn: syscallConn,
|
||||||
|
rawSyscallConn: rawSyscallConn,
|
||||||
|
}
|
||||||
|
err = kConn.setupKernel(txOffload, rxOffload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return kConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SyscallConnForRead() syscall.RawConn {
|
||||||
|
if !c.kernelRx {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !*c.rawConn.IsClient {
|
||||||
|
c.logger.WarnContext(c.ctx, "ktls: RX splice is unavailable on the server size, since it will cause an unknown failure")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.logger.DebugContext(c.ctx, "ktls: RX splice requested")
|
||||||
|
return c.rawSyscallConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) HandleSyscallReadError(inputErr error) ([]byte, error) {
|
||||||
|
if errors.Is(inputErr, unix.EINVAL) {
|
||||||
|
c.pendingRxSplice = true
|
||||||
|
err := c.readRecord()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "ktls: handle non-application-data record")
|
||||||
|
}
|
||||||
|
var input bytes.Buffer
|
||||||
|
if c.rawConn.Input.Len() > 0 {
|
||||||
|
_, err = c.rawConn.Input.WriteTo(&input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input.Bytes(), nil
|
||||||
|
} else if errors.Is(inputErr, unix.EBADMSG) {
|
||||||
|
return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertBadRecordMAC))
|
||||||
|
} else {
|
||||||
|
return nil, E.Cause(inputErr, "ktls: unexpected errno")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SyscallConnForWrite() syscall.RawConn {
|
||||||
|
if !c.kernelTx {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.logger.DebugContext(c.ctx, "ktls: TX splice requested")
|
||||||
|
return c.rawSyscallConn
|
||||||
|
}
|
||||||
80
common/ktls/ktls_alert.go
Normal file
80
common/ktls/ktls_alert.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// alert level
|
||||||
|
alertLevelWarning = 1
|
||||||
|
alertLevelError = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
alertCloseNotify = 0
|
||||||
|
alertUnexpectedMessage = 10
|
||||||
|
alertBadRecordMAC = 20
|
||||||
|
alertDecryptionFailed = 21
|
||||||
|
alertRecordOverflow = 22
|
||||||
|
alertDecompressionFailure = 30
|
||||||
|
alertHandshakeFailure = 40
|
||||||
|
alertBadCertificate = 42
|
||||||
|
alertUnsupportedCertificate = 43
|
||||||
|
alertCertificateRevoked = 44
|
||||||
|
alertCertificateExpired = 45
|
||||||
|
alertCertificateUnknown = 46
|
||||||
|
alertIllegalParameter = 47
|
||||||
|
alertUnknownCA = 48
|
||||||
|
alertAccessDenied = 49
|
||||||
|
alertDecodeError = 50
|
||||||
|
alertDecryptError = 51
|
||||||
|
alertExportRestriction = 60
|
||||||
|
alertProtocolVersion = 70
|
||||||
|
alertInsufficientSecurity = 71
|
||||||
|
alertInternalError = 80
|
||||||
|
alertInappropriateFallback = 86
|
||||||
|
alertUserCanceled = 90
|
||||||
|
alertNoRenegotiation = 100
|
||||||
|
alertMissingExtension = 109
|
||||||
|
alertUnsupportedExtension = 110
|
||||||
|
alertCertificateUnobtainable = 111
|
||||||
|
alertUnrecognizedName = 112
|
||||||
|
alertBadCertificateStatusResponse = 113
|
||||||
|
alertBadCertificateHashValue = 114
|
||||||
|
alertUnknownPSKIdentity = 115
|
||||||
|
alertCertificateRequired = 116
|
||||||
|
alertNoApplicationProtocol = 120
|
||||||
|
alertECHRequired = 121
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Conn) sendAlertLocked(err uint8) error {
|
||||||
|
switch err {
|
||||||
|
case alertNoRenegotiation, alertCloseNotify:
|
||||||
|
c.rawConn.Tmp[0] = alertLevelWarning
|
||||||
|
default:
|
||||||
|
c.rawConn.Tmp[0] = alertLevelError
|
||||||
|
}
|
||||||
|
c.rawConn.Tmp[1] = byte(err)
|
||||||
|
|
||||||
|
_, writeErr := c.writeRecordLocked(recordTypeAlert, c.rawConn.Tmp[0:2])
|
||||||
|
if err == alertCloseNotify {
|
||||||
|
// closeNotify is a special case in that it isn't an error.
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.rawConn.Out.SetErrorLocked(&net.OpError{Op: "local error", Err: tls.AlertError(err)})
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendAlert sends a TLS alert message.
|
||||||
|
func (c *Conn) sendAlert(err uint8) error {
|
||||||
|
c.rawConn.Out.Lock()
|
||||||
|
defer c.rawConn.Out.Unlock()
|
||||||
|
return c.sendAlertLocked(err)
|
||||||
|
}
|
||||||
326
common/ktls/ktls_cipher_suites_linux.go
Normal file
326
common/ktls/ktls_cipher_suites_linux.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kernelCryptoCipherType uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLS_CIPHER_AES_GCM_128 kernelCryptoCipherType = 51
|
||||||
|
TLS_CIPHER_AES_GCM_128_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
TLS_CIPHER_AES_GCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_AES_GCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
TLS_CIPHER_AES_GCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
TLS_CIPHER_AES_GCM_256 kernelCryptoCipherType = 52
|
||||||
|
TLS_CIPHER_AES_GCM_256_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
TLS_CIPHER_AES_GCM_256_KEY_SIZE kernelCryptoCipherType = 32
|
||||||
|
TLS_CIPHER_AES_GCM_256_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
TLS_CIPHER_AES_GCM_256_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
TLS_CIPHER_AES_CCM_128 kernelCryptoCipherType = 53
|
||||||
|
TLS_CIPHER_AES_CCM_128_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
TLS_CIPHER_AES_CCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_AES_CCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
TLS_CIPHER_AES_CCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305 kernelCryptoCipherType = 54
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE kernelCryptoCipherType = 12
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE kernelCryptoCipherType = 32
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE kernelCryptoCipherType = 0
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
// TLS_CIPHER_SM4_GCM kernelCryptoCipherType = 55
|
||||||
|
// TLS_CIPHER_SM4_GCM_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
// TLS_CIPHER_SM4_GCM_KEY_SIZE kernelCryptoCipherType = 16
|
||||||
|
// TLS_CIPHER_SM4_GCM_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
// TLS_CIPHER_SM4_GCM_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
// TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
// TLS_CIPHER_SM4_CCM kernelCryptoCipherType = 56
|
||||||
|
// TLS_CIPHER_SM4_CCM_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
// TLS_CIPHER_SM4_CCM_KEY_SIZE kernelCryptoCipherType = 16
|
||||||
|
// TLS_CIPHER_SM4_CCM_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
// TLS_CIPHER_SM4_CCM_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
// TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
TLS_CIPHER_ARIA_GCM_128 kernelCryptoCipherType = 57
|
||||||
|
TLS_CIPHER_ARIA_GCM_128_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
TLS_CIPHER_ARIA_GCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_ARIA_GCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
TLS_CIPHER_ARIA_GCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
|
||||||
|
TLS_CIPHER_ARIA_GCM_256 kernelCryptoCipherType = 58
|
||||||
|
TLS_CIPHER_ARIA_GCM_256_IV_SIZE kernelCryptoCipherType = 8
|
||||||
|
TLS_CIPHER_ARIA_GCM_256_KEY_SIZE kernelCryptoCipherType = 32
|
||||||
|
TLS_CIPHER_ARIA_GCM_256_SALT_SIZE kernelCryptoCipherType = 4
|
||||||
|
TLS_CIPHER_ARIA_GCM_256_TAG_SIZE kernelCryptoCipherType = 16
|
||||||
|
TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
type kernelCrypto interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type kernelCryptoInfo struct {
|
||||||
|
version uint16
|
||||||
|
cipher_type kernelCryptoCipherType
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoAES128GCM{}
|
||||||
|
|
||||||
|
type kernelCryptoAES128GCM struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_AES_GCM_128_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_AES_GCM_128_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_AES_GCM_128_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoAES128GCM) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_AES_GCM_128
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoAES256GCM{}
|
||||||
|
|
||||||
|
type kernelCryptoAES256GCM struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_AES_GCM_256_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_AES_GCM_256_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_AES_GCM_256_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoAES256GCM) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_AES_GCM_256
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoAES128CCM{}
|
||||||
|
|
||||||
|
type kernelCryptoAES128CCM struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_AES_CCM_128_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_AES_CCM_128_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_AES_CCM_128_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoAES128CCM) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_AES_CCM_128
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoChacha20Poly1035{}
|
||||||
|
|
||||||
|
type kernelCryptoChacha20Poly1035 struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoChacha20Poly1035) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_CHACHA20_POLY1305
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// var _ kernelCrypto = &kernelCryptoSM4GCM{}
|
||||||
|
|
||||||
|
// type kernelCryptoSM4GCM struct {
|
||||||
|
// kernelCryptoInfo
|
||||||
|
// iv [TLS_CIPHER_SM4_GCM_IV_SIZE]byte
|
||||||
|
// key [TLS_CIPHER_SM4_GCM_KEY_SIZE]byte
|
||||||
|
// salt [TLS_CIPHER_SM4_GCM_SALT_SIZE]byte
|
||||||
|
// rec_seq [TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE]byte
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (crypto *kernelCryptoSM4GCM) String() string {
|
||||||
|
// crypto.cipher_type = TLS_CIPHER_SM4_GCM
|
||||||
|
// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var _ kernelCrypto = &kernelCryptoSM4CCM{}
|
||||||
|
|
||||||
|
// type kernelCryptoSM4CCM struct {
|
||||||
|
// kernelCryptoInfo
|
||||||
|
// iv [TLS_CIPHER_SM4_CCM_IV_SIZE]byte
|
||||||
|
// key [TLS_CIPHER_SM4_CCM_KEY_SIZE]byte
|
||||||
|
// salt [TLS_CIPHER_SM4_CCM_SALT_SIZE]byte
|
||||||
|
// rec_seq [TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE]byte
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (crypto *kernelCryptoSM4CCM) String() string {
|
||||||
|
// crypto.cipher_type = TLS_CIPHER_SM4_CCM
|
||||||
|
// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
// }
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoARIA128GCM{}
|
||||||
|
|
||||||
|
type kernelCryptoARIA128GCM struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_ARIA_GCM_128_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_ARIA_GCM_128_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_ARIA_GCM_128_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoARIA128GCM) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_ARIA_GCM_128
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kernelCrypto = &kernelCryptoARIA256GCM{}
|
||||||
|
|
||||||
|
type kernelCryptoARIA256GCM struct {
|
||||||
|
kernelCryptoInfo
|
||||||
|
iv [TLS_CIPHER_ARIA_GCM_256_IV_SIZE]byte
|
||||||
|
key [TLS_CIPHER_ARIA_GCM_256_KEY_SIZE]byte
|
||||||
|
salt [TLS_CIPHER_ARIA_GCM_256_SALT_SIZE]byte
|
||||||
|
rec_seq [TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (crypto *kernelCryptoARIA256GCM) String() string {
|
||||||
|
crypto.cipher_type = TLS_CIPHER_ARIA_GCM_256
|
||||||
|
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func kernelCipher(kernel *Support, hc *badtls.RawHalfConn, cipherSuite uint16, isRX bool) kernelCrypto {
|
||||||
|
if !kernel.TLS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *hc.Version {
|
||||||
|
case tls.VersionTLS12:
|
||||||
|
if isRX && !kernel.TLS_Version13_RX {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case tls.VersionTLS13:
|
||||||
|
if !kernel.TLS_Version13 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRX && !kernel.TLS_Version13_RX {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var key, iv []byte
|
||||||
|
if *hc.Version == tls.VersionTLS13 {
|
||||||
|
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), *hc.TrafficSecret)
|
||||||
|
/*if isRX {
|
||||||
|
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.RemoteTrafficSecret)
|
||||||
|
} else {
|
||||||
|
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.TrafficSecret)
|
||||||
|
}*/
|
||||||
|
} else {
|
||||||
|
// csPtr := cipherSuiteByID(cipherSuite)
|
||||||
|
// keysFromMasterSecret(*hc.Version, csPtr, keyLog.Secret, keyLog.Random)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cipherSuite {
|
||||||
|
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
||||||
|
crypto := new(kernelCryptoAES128GCM)
|
||||||
|
|
||||||
|
crypto.version = *hc.Version
|
||||||
|
copy(crypto.key[:], key)
|
||||||
|
copy(crypto.iv[:], iv[4:])
|
||||||
|
copy(crypto.salt[:], iv[:4])
|
||||||
|
crypto.rec_seq = *hc.Seq
|
||||||
|
|
||||||
|
return crypto
|
||||||
|
case tls.TLS_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
|
||||||
|
if !kernel.TLS_AES_256_GCM {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto := new(kernelCryptoAES256GCM)
|
||||||
|
|
||||||
|
crypto.version = *hc.Version
|
||||||
|
copy(crypto.key[:], key)
|
||||||
|
copy(crypto.iv[:], iv[4:])
|
||||||
|
copy(crypto.salt[:], iv[:4])
|
||||||
|
crypto.rec_seq = *hc.Seq
|
||||||
|
|
||||||
|
return crypto
|
||||||
|
//case tls.TLS_AES_128_CCM_SHA256, tls.TLS_RSA_WITH_AES_128_CCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_SHA256:
|
||||||
|
// if !kernel.TLS_AES_128_CCM {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// crypto := new(kernelCryptoAES128CCM)
|
||||||
|
//
|
||||||
|
// crypto.version = *hc.Version
|
||||||
|
// copy(crypto.key[:], key)
|
||||||
|
// copy(crypto.iv[:], iv[4:])
|
||||||
|
// copy(crypto.salt[:], iv[:4])
|
||||||
|
// crypto.rec_seq = *hc.Seq
|
||||||
|
//
|
||||||
|
// return crypto
|
||||||
|
case tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
|
||||||
|
if !kernel.TLS_CHACHA20_POLY1305 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto := new(kernelCryptoChacha20Poly1035)
|
||||||
|
|
||||||
|
crypto.version = *hc.Version
|
||||||
|
copy(crypto.key[:], key)
|
||||||
|
copy(crypto.iv[:], iv)
|
||||||
|
crypto.rec_seq = *hc.Seq
|
||||||
|
|
||||||
|
return crypto
|
||||||
|
//case tls.TLS_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
|
||||||
|
// if !kernel.TLS_ARIA_GCM {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// crypto := new(kernelCryptoARIA128GCM)
|
||||||
|
//
|
||||||
|
// crypto.version = *hc.Version
|
||||||
|
// copy(crypto.key[:], key)
|
||||||
|
// copy(crypto.iv[:], iv[4:])
|
||||||
|
// copy(crypto.salt[:], iv[:4])
|
||||||
|
// crypto.rec_seq = *hc.Seq
|
||||||
|
//
|
||||||
|
// return crypto
|
||||||
|
//case tls.TLS_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
|
||||||
|
// if !kernel.TLS_ARIA_GCM {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// crypto := new(kernelCryptoARIA256GCM)
|
||||||
|
//
|
||||||
|
// crypto.version = *hc.Version
|
||||||
|
// copy(crypto.key[:], key)
|
||||||
|
// copy(crypto.iv[:], iv[4:])
|
||||||
|
// copy(crypto.salt[:], iv[:4])
|
||||||
|
// crypto.rec_seq = *hc.Seq
|
||||||
|
//
|
||||||
|
// return crypto
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
67
common/ktls/ktls_close.go
Normal file
67
common/ktls/ktls_close.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
if !c.kernelTx {
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interlock with Conn.Write above.
|
||||||
|
var x int32
|
||||||
|
for {
|
||||||
|
x = c.rawConn.ActiveCall.Load()
|
||||||
|
if x&1 != 0 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
if c.rawConn.ActiveCall.CompareAndSwap(x, x|1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x != 0 {
|
||||||
|
// io.Writer and io.Closer should not be used concurrently.
|
||||||
|
// If Close is called while a Write is currently in-flight,
|
||||||
|
// interpret that as a sign that this Close is really just
|
||||||
|
// being used to break the Write and/or clean up resources and
|
||||||
|
// avoid sending the alertCloseNotify, which may block
|
||||||
|
// waiting on handshakeMutex or the c.out mutex.
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var alertErr error
|
||||||
|
if c.rawConn.IsHandshakeComplete.Load() {
|
||||||
|
if err := c.closeNotify(); err != nil {
|
||||||
|
alertErr = fmt.Errorf("tls: failed to send closeNotify alert (but connection was closed anyway): %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return alertErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) closeNotify() error {
|
||||||
|
c.rawConn.Out.Lock()
|
||||||
|
defer c.rawConn.Out.Unlock()
|
||||||
|
|
||||||
|
if !*c.rawConn.CloseNotifySent {
|
||||||
|
// Set a Write Deadline to prevent possibly blocking forever.
|
||||||
|
c.SetWriteDeadline(time.Now().Add(time.Second * 5))
|
||||||
|
*c.rawConn.CloseNotifyErr = c.sendAlertLocked(alertCloseNotify)
|
||||||
|
*c.rawConn.CloseNotifySent = true
|
||||||
|
// Any subsequent writes will fail.
|
||||||
|
c.SetWriteDeadline(time.Now())
|
||||||
|
}
|
||||||
|
return *c.rawConn.CloseNotifyErr
|
||||||
|
}
|
||||||
24
common/ktls/ktls_const.go
Normal file
24
common/ktls/ktls_const.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPlaintext = 16384 // maximum plaintext payload length
|
||||||
|
maxCiphertext = 16384 + 2048 // maximum ciphertext payload length
|
||||||
|
maxCiphertextTLS13 = 16384 + 256 // maximum ciphertext length in TLS 1.3
|
||||||
|
recordHeaderLen = 5 // record header length
|
||||||
|
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
|
||||||
|
maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB)
|
||||||
|
maxUselessRecords = 16 // maximum number of consecutive non-advancing records
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
recordTypeChangeCipherSpec = 20
|
||||||
|
recordTypeAlert = 21
|
||||||
|
recordTypeHandshake = 22
|
||||||
|
recordTypeApplicationData = 23
|
||||||
|
)
|
||||||
238
common/ktls/ktls_handshake_messages.go
Normal file
238
common/ktls/ktls_handshake_messages.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The marshalingFunction type is an adapter to allow the use of ordinary
|
||||||
|
// functions as cryptobyte.MarshalingValue.
|
||||||
|
type marshalingFunction func(b *cryptobyte.Builder) error
|
||||||
|
|
||||||
|
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
|
||||||
|
// the length of the sequence is not the value specified, it produces an error.
|
||||||
|
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
|
||||||
|
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
|
||||||
|
if len(v) != n {
|
||||||
|
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
|
||||||
|
}
|
||||||
|
b.AddBytes(v)
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
|
||||||
|
func addUint64(b *cryptobyte.Builder, v uint64) {
|
||||||
|
b.AddUint32(uint32(v >> 32))
|
||||||
|
b.AddUint32(uint32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func readUint64(s *cryptobyte.String, out *uint64) bool {
|
||||||
|
var hi, lo uint32
|
||||||
|
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint64(hi)<<32 | uint64(lo)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
|
||||||
|
// []byte instead of a cryptobyte.String.
|
||||||
|
func readUint8LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
||||||
|
return s.ReadUint8LengthPrefixed((*cryptobyte.String)(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUint16LengthPrefixed acts like s.ReadUint16LengthPrefixed, but targets a
|
||||||
|
// []byte instead of a cryptobyte.String.
|
||||||
|
func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
||||||
|
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
|
||||||
|
// []byte instead of a cryptobyte.String.
|
||||||
|
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
||||||
|
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyUpdateMsg struct {
|
||||||
|
updateRequested bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *keyUpdateMsg) marshal() ([]byte, error) {
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddUint8(typeKeyUpdate)
|
||||||
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
if m.updateRequested {
|
||||||
|
b.AddUint8(1)
|
||||||
|
} else {
|
||||||
|
b.AddUint8(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *keyUpdateMsg) unmarshal(data []byte) bool {
|
||||||
|
s := cryptobyte.String(data)
|
||||||
|
|
||||||
|
var updateRequested uint8
|
||||||
|
if !s.Skip(4) || // message type and uint24 length field
|
||||||
|
!s.ReadUint8(&updateRequested) || !s.Empty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch updateRequested {
|
||||||
|
case 0:
|
||||||
|
m.updateRequested = false
|
||||||
|
case 1:
|
||||||
|
m.updateRequested = true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS handshake message types.
|
||||||
|
const (
|
||||||
|
typeHelloRequest uint8 = 0
|
||||||
|
typeClientHello uint8 = 1
|
||||||
|
typeServerHello uint8 = 2
|
||||||
|
typeNewSessionTicket uint8 = 4
|
||||||
|
typeEndOfEarlyData uint8 = 5
|
||||||
|
typeEncryptedExtensions uint8 = 8
|
||||||
|
typeCertificate uint8 = 11
|
||||||
|
typeServerKeyExchange uint8 = 12
|
||||||
|
typeCertificateRequest uint8 = 13
|
||||||
|
typeServerHelloDone uint8 = 14
|
||||||
|
typeCertificateVerify uint8 = 15
|
||||||
|
typeClientKeyExchange uint8 = 16
|
||||||
|
typeFinished uint8 = 20
|
||||||
|
typeCertificateStatus uint8 = 22
|
||||||
|
typeKeyUpdate uint8 = 24
|
||||||
|
typeCompressedCertificate uint8 = 25
|
||||||
|
typeMessageHash uint8 = 254 // synthetic message
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLS compression types.
|
||||||
|
const (
|
||||||
|
compressionNone uint8 = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLS extension numbers
|
||||||
|
const (
|
||||||
|
extensionServerName uint16 = 0
|
||||||
|
extensionStatusRequest uint16 = 5
|
||||||
|
extensionSupportedCurves uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7
|
||||||
|
extensionSupportedPoints uint16 = 11
|
||||||
|
extensionSignatureAlgorithms uint16 = 13
|
||||||
|
extensionALPN uint16 = 16
|
||||||
|
extensionSCT uint16 = 18
|
||||||
|
extensionPadding uint16 = 21
|
||||||
|
extensionExtendedMasterSecret uint16 = 23
|
||||||
|
extensionCompressCertificate uint16 = 27 // compress_certificate in TLS 1.3
|
||||||
|
extensionSessionTicket uint16 = 35
|
||||||
|
extensionPreSharedKey uint16 = 41
|
||||||
|
extensionEarlyData uint16 = 42
|
||||||
|
extensionSupportedVersions uint16 = 43
|
||||||
|
extensionCookie uint16 = 44
|
||||||
|
extensionPSKModes uint16 = 45
|
||||||
|
extensionCertificateAuthorities uint16 = 47
|
||||||
|
extensionSignatureAlgorithmsCert uint16 = 50
|
||||||
|
extensionKeyShare uint16 = 51
|
||||||
|
extensionQUICTransportParameters uint16 = 57
|
||||||
|
extensionALPS uint16 = 17513
|
||||||
|
extensionRenegotiationInfo uint16 = 0xff01
|
||||||
|
extensionECHOuterExtensions uint16 = 0xfd00
|
||||||
|
extensionEncryptedClientHello uint16 = 0xfe0d
|
||||||
|
)
|
||||||
|
|
||||||
|
type handshakeMessage interface {
|
||||||
|
marshal() ([]byte, error)
|
||||||
|
unmarshal([]byte) bool
|
||||||
|
}
|
||||||
|
type newSessionTicketMsgTLS13 struct {
|
||||||
|
lifetime uint32
|
||||||
|
ageAdd uint32
|
||||||
|
nonce []byte
|
||||||
|
label []byte
|
||||||
|
maxEarlyData uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *newSessionTicketMsgTLS13) marshal() ([]byte, error) {
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddUint8(typeNewSessionTicket)
|
||||||
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
b.AddUint32(m.lifetime)
|
||||||
|
b.AddUint32(m.ageAdd)
|
||||||
|
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
b.AddBytes(m.nonce)
|
||||||
|
})
|
||||||
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
b.AddBytes(m.label)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
if m.maxEarlyData > 0 {
|
||||||
|
b.AddUint16(extensionEarlyData)
|
||||||
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
b.AddUint32(m.maxEarlyData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool {
|
||||||
|
*m = newSessionTicketMsgTLS13{}
|
||||||
|
s := cryptobyte.String(data)
|
||||||
|
|
||||||
|
var extensions cryptobyte.String
|
||||||
|
if !s.Skip(4) || // message type and uint24 length field
|
||||||
|
!s.ReadUint32(&m.lifetime) ||
|
||||||
|
!s.ReadUint32(&m.ageAdd) ||
|
||||||
|
!readUint8LengthPrefixed(&s, &m.nonce) ||
|
||||||
|
!readUint16LengthPrefixed(&s, &m.label) ||
|
||||||
|
!s.ReadUint16LengthPrefixed(&extensions) ||
|
||||||
|
!s.Empty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for !extensions.Empty() {
|
||||||
|
var extension uint16
|
||||||
|
var extData cryptobyte.String
|
||||||
|
if !extensions.ReadUint16(&extension) ||
|
||||||
|
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch extension {
|
||||||
|
case extensionEarlyData:
|
||||||
|
if !extData.ReadUint32(&m.maxEarlyData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Ignore unknown extensions.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !extData.Empty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
173
common/ktls/ktls_key_update.go
Normal file
173
common/ktls/ktls_key_update.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handlePostHandshakeMessage processes a handshake message arrived after the
|
||||||
|
// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation.
|
||||||
|
func (c *Conn) handlePostHandshakeMessage() error {
|
||||||
|
if *c.rawConn.Vers != tls.VersionTLS13 {
|
||||||
|
return errors.New("ktls: kernel does not support TLS 1.2 renegotiation")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := c.readHandshake(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//c.retryCount++
|
||||||
|
//if c.retryCount > maxUselessRecords {
|
||||||
|
// c.sendAlert(alertUnexpectedMessage)
|
||||||
|
// return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
||||||
|
//}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *newSessionTicketMsgTLS13:
|
||||||
|
// return errors.New("ktls: received new session ticket")
|
||||||
|
return nil
|
||||||
|
case *keyUpdateMsg:
|
||||||
|
return c.handleKeyUpdate(msg)
|
||||||
|
}
|
||||||
|
// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest
|
||||||
|
// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an
|
||||||
|
// unexpected_message alert here doesn't provide it with enough information to distinguish
|
||||||
|
// this condition from other unexpected messages. This is probably fine.
|
||||||
|
c.sendAlert(alertUnexpectedMessage)
|
||||||
|
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
|
||||||
|
//if c.quic != nil {
|
||||||
|
// c.sendAlert(alertUnexpectedMessage)
|
||||||
|
// return c.in.setErrorLocked(errors.New("tls: received unexpected key update message"))
|
||||||
|
//}
|
||||||
|
|
||||||
|
cipherSuite := cipherSuiteTLS13ByID(*c.rawConn.CipherSuite)
|
||||||
|
if cipherSuite == nil {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertInternalError))
|
||||||
|
}
|
||||||
|
|
||||||
|
newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.In.TrafficSecret)
|
||||||
|
c.rawConn.In.SetTrafficSecret(cipherSuite, 0 /*tls.QUICEncryptionLevelInitial*/, newSecret)
|
||||||
|
|
||||||
|
err := c.resetupRX()
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlert(alertInternalError)
|
||||||
|
return c.rawConn.In.SetErrorLocked(fmt.Errorf("ktls: resetupRX failed: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyUpdate.updateRequested {
|
||||||
|
c.rawConn.Out.Lock()
|
||||||
|
defer c.rawConn.Out.Unlock()
|
||||||
|
|
||||||
|
resetup, err := c.resetupTX()
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlertLocked(alertInternalError)
|
||||||
|
return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &keyUpdateMsg{}
|
||||||
|
msgBytes, err := msg.marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.writeRecordLocked(recordTypeHandshake, msgBytes)
|
||||||
|
if err != nil {
|
||||||
|
// Surface the error at the next write.
|
||||||
|
c.rawConn.Out.SetErrorLocked(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.Out.TrafficSecret)
|
||||||
|
c.rawConn.Out.SetTrafficSecret(cipherSuite, 0 /*QUICEncryptionLevelInitial*/, newSecret)
|
||||||
|
|
||||||
|
err = resetup()
|
||||||
|
if err != nil {
|
||||||
|
return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) readHandshakeBytes(n int) error {
|
||||||
|
//if c.quic != nil {
|
||||||
|
// return c.quicReadHandshakeBytes(n)
|
||||||
|
//}
|
||||||
|
for c.rawConn.Hand.Len() < n {
|
||||||
|
if err := c.readRecord(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) readHandshake(transcript io.Writer) (any, error) {
|
||||||
|
if err := c.readHandshakeBytes(4); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := c.rawConn.Hand.Bytes()
|
||||||
|
|
||||||
|
maxHandshakeSize := maxHandshake
|
||||||
|
// hasVers indicates we're past the first message, forcing someone trying to
|
||||||
|
// make us just allocate a large buffer to at least do the initial part of
|
||||||
|
// the handshake first.
|
||||||
|
//if c.haveVers && data[0] == typeCertificate {
|
||||||
|
// Since certificate messages are likely to be the only messages that
|
||||||
|
// can be larger than maxHandshake, we use a special limit for just
|
||||||
|
// those messages.
|
||||||
|
//maxHandshakeSize = maxHandshakeCertificateMsg
|
||||||
|
//}
|
||||||
|
|
||||||
|
n := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
|
||||||
|
if n > maxHandshakeSize {
|
||||||
|
c.sendAlertLocked(alertInternalError)
|
||||||
|
return nil, c.rawConn.In.SetErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshakeSize))
|
||||||
|
}
|
||||||
|
if err := c.readHandshakeBytes(4 + n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = c.rawConn.Hand.Next(4 + n)
|
||||||
|
return c.unmarshalHandshakeMessage(data, transcript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript io.Writer) (any, error) {
|
||||||
|
var m handshakeMessage
|
||||||
|
switch data[0] {
|
||||||
|
case typeNewSessionTicket:
|
||||||
|
if *c.rawConn.Vers == tls.VersionTLS13 {
|
||||||
|
m = new(newSessionTicketMsgTLS13)
|
||||||
|
} else {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
case typeKeyUpdate:
|
||||||
|
m = new(keyUpdateMsg)
|
||||||
|
default:
|
||||||
|
return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The handshake message unmarshalers
|
||||||
|
// expect to be able to keep references to data,
|
||||||
|
// so pass in a fresh copy that won't be overwritten.
|
||||||
|
data = append([]byte(nil), data...)
|
||||||
|
|
||||||
|
if !m.unmarshal(data) {
|
||||||
|
return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if transcript != nil {
|
||||||
|
transcript.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
329
common/ktls/ktls_linux.go
Normal file
329
common/ktls/ktls_linux.go
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badversion"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mod from https://gitlab.com/go-extension/tls
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLS_TX = 1
|
||||||
|
TLS_RX = 2
|
||||||
|
TLS_TX_ZEROCOPY_RO = 3 // TX zerocopy (only sendfile now)
|
||||||
|
TLS_RX_EXPECT_NO_PAD = 4 // Attempt opportunistic zero-copy, TLS 1.3 only
|
||||||
|
|
||||||
|
TLS_SET_RECORD_TYPE = 1
|
||||||
|
TLS_GET_RECORD_TYPE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Support struct {
|
||||||
|
TLS, TLS_RX bool
|
||||||
|
TLS_Version13, TLS_Version13_RX bool
|
||||||
|
|
||||||
|
TLS_TX_ZEROCOPY bool
|
||||||
|
TLS_RX_NOPADDING bool
|
||||||
|
|
||||||
|
TLS_AES_256_GCM bool
|
||||||
|
TLS_AES_128_CCM bool
|
||||||
|
TLS_CHACHA20_POLY1305 bool
|
||||||
|
TLS_SM4 bool
|
||||||
|
TLS_ARIA_GCM bool
|
||||||
|
|
||||||
|
TLS_Version13_KeyUpdate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var KernelSupport = sync.OnceValues(func() (*Support, error) {
|
||||||
|
var uname unix.Utsname
|
||||||
|
err := unix.Uname(&uname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kernelVersion := badversion.Parse(strings.Trim(string(uname.Release[:]), "\x00"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var support Support
|
||||||
|
switch {
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 14}):
|
||||||
|
support.TLS_Version13_KeyUpdate = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 1}):
|
||||||
|
support.TLS_ARIA_GCM = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6}):
|
||||||
|
support.TLS_Version13_RX = true
|
||||||
|
support.TLS_RX_NOPADDING = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 19}):
|
||||||
|
support.TLS_TX_ZEROCOPY = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 16}):
|
||||||
|
support.TLS_SM4 = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 11}):
|
||||||
|
support.TLS_CHACHA20_POLY1305 = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 2}):
|
||||||
|
support.TLS_AES_128_CCM = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 1}):
|
||||||
|
support.TLS_AES_256_GCM = true
|
||||||
|
support.TLS_Version13 = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 17}):
|
||||||
|
support.TLS_RX = true
|
||||||
|
fallthrough
|
||||||
|
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 13}):
|
||||||
|
support.TLS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if support.TLS && support.TLS_Version13 {
|
||||||
|
_, err := os.Stat("/sys/module/tls")
|
||||||
|
if err != nil {
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
output, err := shell.Exec("modprobe", "tls").Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Extend(E.Cause(err, "modprobe tls"), output)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, E.New("ktls: kernel TLS module not loaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &support, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
func Load() error {
|
||||||
|
support, err := KernelSupport()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ktls: check availability")
|
||||||
|
}
|
||||||
|
if !support.TLS || !support.TLS_Version13 {
|
||||||
|
return E.New("ktls: kernel does not support TLS 1.3")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) setupKernel(txOffload, rxOffload bool) error {
|
||||||
|
if !txOffload && !rxOffload {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
support, err := KernelSupport()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "check availability")
|
||||||
|
}
|
||||||
|
if !support.TLS || !support.TLS_Version13 {
|
||||||
|
return E.New("kernel does not support TLS 1.3")
|
||||||
|
}
|
||||||
|
c.rawConn.Out.Lock()
|
||||||
|
defer c.rawConn.Out.Unlock()
|
||||||
|
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("setsockopt", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if txOffload {
|
||||||
|
txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)
|
||||||
|
if txCrypto == nil {
|
||||||
|
return E.New("unsupported cipher suite")
|
||||||
|
}
|
||||||
|
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if support.TLS_TX_ZEROCOPY {
|
||||||
|
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_TX_ZEROCOPY_RO, 1)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.kernelTx = true
|
||||||
|
c.logger.DebugContext(c.ctx, "ktls: kernel TLS TX enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rxOffload {
|
||||||
|
rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)
|
||||||
|
if rxCrypto == nil {
|
||||||
|
return E.New("unsupported cipher suite")
|
||||||
|
}
|
||||||
|
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *c.rawConn.Vers >= tls.VersionTLS13 && support.TLS_RX_NOPADDING {
|
||||||
|
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_RX_EXPECT_NO_PAD, 1)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.kernelRx = true
|
||||||
|
c.logger.DebugContext(c.ctx, "ktls: kernel TLS RX enabled")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) resetupTX() (func() error, error) {
|
||||||
|
if !c.kernelTx {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
support, err := KernelSupport()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !support.TLS_Version13_KeyUpdate {
|
||||||
|
return nil, errors.New("ktls: kernel does not support rekey")
|
||||||
|
}
|
||||||
|
txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)
|
||||||
|
if txCrypto == nil {
|
||||||
|
return nil, errors.New("ktls: set kernelCipher on unsupported tls session")
|
||||||
|
}
|
||||||
|
return func() error {
|
||||||
|
return control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())
|
||||||
|
})
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) resetupRX() error {
|
||||||
|
if !c.kernelRx {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
support, err := KernelSupport()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !support.TLS_Version13_KeyUpdate {
|
||||||
|
return errors.New("ktls: kernel does not support rekey")
|
||||||
|
}
|
||||||
|
rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)
|
||||||
|
if rxCrypto == nil {
|
||||||
|
return errors.New("ktls: set kernelCipher on unsupported tls session")
|
||||||
|
}
|
||||||
|
return control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
||||||
|
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) readKernelRecord() (uint8, []byte, error) {
|
||||||
|
if c.rawConn.RawInput.Len() < maxPlaintext {
|
||||||
|
c.rawConn.RawInput.Grow(maxPlaintext - c.rawConn.RawInput.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
data := c.rawConn.RawInput.Bytes()[:maxPlaintext]
|
||||||
|
|
||||||
|
// cmsg for record type
|
||||||
|
buffer := make([]byte, unix.CmsgSpace(1))
|
||||||
|
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))
|
||||||
|
cmsg.SetLen(unix.CmsgLen(1))
|
||||||
|
|
||||||
|
var iov unix.Iovec
|
||||||
|
iov.Base = &data[0]
|
||||||
|
iov.SetLen(len(data))
|
||||||
|
|
||||||
|
var msg unix.Msghdr
|
||||||
|
msg.Control = &buffer[0]
|
||||||
|
msg.Controllen = cmsg.Len
|
||||||
|
msg.Iov = &iov
|
||||||
|
msg.Iovlen = 1
|
||||||
|
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
er := c.rawSyscallConn.Read(func(fd uintptr) bool {
|
||||||
|
n, err = recvmsg(int(fd), &msg, 0)
|
||||||
|
return err != unix.EAGAIN || c.pendingRxSplice
|
||||||
|
})
|
||||||
|
if er != nil {
|
||||||
|
return 0, nil, er
|
||||||
|
}
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
case syscall.EINVAL, syscall.EAGAIN:
|
||||||
|
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion))
|
||||||
|
case syscall.EMSGSIZE:
|
||||||
|
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))
|
||||||
|
case syscall.EBADMSG:
|
||||||
|
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecryptError))
|
||||||
|
default:
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n <= 0 {
|
||||||
|
return 0, nil, c.rawConn.In.SetErrorLocked(io.EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE {
|
||||||
|
typ := buffer[unix.CmsgLen(0)]
|
||||||
|
return typ, data[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return recordTypeApplicationData, data[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) {
|
||||||
|
if typ == recordTypeApplicationData {
|
||||||
|
return c.conn.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmsg for record type
|
||||||
|
buffer := make([]byte, unix.CmsgSpace(1))
|
||||||
|
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))
|
||||||
|
cmsg.SetLen(unix.CmsgLen(1))
|
||||||
|
buffer[unix.CmsgLen(0)] = byte(typ)
|
||||||
|
cmsg.Level = unix.SOL_TLS
|
||||||
|
cmsg.Type = TLS_SET_RECORD_TYPE
|
||||||
|
|
||||||
|
var iov unix.Iovec
|
||||||
|
iov.Base = &data[0]
|
||||||
|
iov.SetLen(len(data))
|
||||||
|
|
||||||
|
var msg unix.Msghdr
|
||||||
|
msg.Control = &buffer[0]
|
||||||
|
msg.Controllen = cmsg.Len
|
||||||
|
msg.Iov = &iov
|
||||||
|
msg.Iovlen = 1
|
||||||
|
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
ew := c.rawSyscallConn.Write(func(fd uintptr) bool {
|
||||||
|
n, err = sendmsg(int(fd), &msg, 0)
|
||||||
|
return err != unix.EAGAIN
|
||||||
|
})
|
||||||
|
if ew != nil {
|
||||||
|
return 0, ew
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname recvmsg golang.org/x/sys/unix.recvmsg
|
||||||
|
func recvmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)
|
||||||
|
|
||||||
|
//go:linkname sendmsg golang.org/x/sys/unix.sendmsg
|
||||||
|
func sendmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)
|
||||||
24
common/ktls/ktls_prf.go
Normal file
24
common/ktls/ktls_prf.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
//go:linkname cipherSuiteByID github.com/metacubex/utls.cipherSuiteByID
|
||||||
|
func cipherSuiteByID(id uint16) unsafe.Pointer
|
||||||
|
|
||||||
|
//go:linkname keysFromMasterSecret github.com/metacubex/utls.keysFromMasterSecret
|
||||||
|
func keysFromMasterSecret(version uint16, suite unsafe.Pointer, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte)
|
||||||
|
|
||||||
|
//go:linkname cipherSuiteTLS13ByID github.com/metacubex/utls.cipherSuiteTLS13ByID
|
||||||
|
func cipherSuiteTLS13ByID(id uint16) unsafe.Pointer
|
||||||
|
|
||||||
|
//go:linkname nextTrafficSecret github.com/metacubex/utls.(*cipherSuiteTLS13).nextTrafficSecret
|
||||||
|
func nextTrafficSecret(cs unsafe.Pointer, trafficSecret []byte) []byte
|
||||||
|
|
||||||
|
//go:linkname trafficKey github.com/metacubex/utls.(*cipherSuiteTLS13).trafficKey
|
||||||
|
func trafficKey(cs unsafe.Pointer, trafficSecret []byte) (key, iv []byte)
|
||||||
292
common/ktls/ktls_read.go
Normal file
292
common/ktls/ktls_read.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && go1.25 && badlinkname
|
||||||
|
|
||||||
|
package ktls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
|
if !c.kernelRx {
|
||||||
|
return c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
// Put this after Handshake, in case people were calling
|
||||||
|
// Read(nil) for the side effect of the Handshake.
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.rawConn.In.Lock()
|
||||||
|
defer c.rawConn.In.Unlock()
|
||||||
|
|
||||||
|
for c.rawConn.Input.Len() == 0 {
|
||||||
|
if err := c.readRecord(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for c.rawConn.Hand.Len() > 0 {
|
||||||
|
if err := c.handlePostHandshakeMessage(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ := c.rawConn.Input.Read(b)
|
||||||
|
|
||||||
|
// If a close-notify alert is waiting, read it so that we can return (n,
|
||||||
|
// EOF) instead of (n, nil), to signal to the HTTP response reading
|
||||||
|
// goroutine that the connection is now closed. This eliminates a race
|
||||||
|
// where the HTTP response reading goroutine would otherwise not observe
|
||||||
|
// the EOF until its next read, by which time a client goroutine might
|
||||||
|
// have already tried to reuse the HTTP connection for a new request.
|
||||||
|
// See https://golang.org/cl/76400046 and https://golang.org/issue/3514
|
||||||
|
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.RawInput.Len() > 0 &&
|
||||||
|
c.rawConn.RawInput.Bytes()[0] == recordTypeAlert {
|
||||||
|
if err := c.readRecord(); err != nil {
|
||||||
|
return n, err // will be io.EOF on closeNotify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) readRecord() error {
|
||||||
|
if *c.rawConn.In.Err != nil {
|
||||||
|
return *c.rawConn.In.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, data, err := c.readRawRecord()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) > maxPlaintext {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application Data messages are always protected.
|
||||||
|
if c.rawConn.In.Cipher == nil && typ == recordTypeApplicationData {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
//if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 {
|
||||||
|
// This is a state-advancing message: reset the retry count.
|
||||||
|
// c.retryCount = 0
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Handshake messages MUST NOT be interleaved with other record types in TLS 1.3.
|
||||||
|
if *c.rawConn.Vers == tls.VersionTLS13 && typ != recordTypeHandshake && c.rawConn.Hand.Len() > 0 {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
default:
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
case recordTypeAlert:
|
||||||
|
//if c.quic != nil {
|
||||||
|
// return c.rawConn.In.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
//}
|
||||||
|
if len(data) != 2 {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
if data[1] == alertCloseNotify {
|
||||||
|
return c.rawConn.In.SetErrorLocked(io.EOF)
|
||||||
|
}
|
||||||
|
if *c.rawConn.Vers == tls.VersionTLS13 {
|
||||||
|
// TLS 1.3 removed warning-level alerts except for alertUserCanceled
|
||||||
|
// (RFC 8446, § 6.1). Since at least one major implementation
|
||||||
|
// (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert,
|
||||||
|
// many TLS stacks now ignore it outright when seen in a TLS 1.3
|
||||||
|
// handshake (e.g. BoringSSL, NSS, Rustls).
|
||||||
|
if data[1] == alertUserCanceled {
|
||||||
|
// Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry.
|
||||||
|
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
||||||
|
}
|
||||||
|
return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])})
|
||||||
|
}
|
||||||
|
switch data[0] {
|
||||||
|
case alertLevelWarning:
|
||||||
|
// Drop the record on the floor and retry.
|
||||||
|
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
||||||
|
case alertLevelError:
|
||||||
|
return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])})
|
||||||
|
default:
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
case recordTypeChangeCipherSpec:
|
||||||
|
if len(data) != 1 || data[0] != 1 {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))
|
||||||
|
}
|
||||||
|
// Handshake messages are not allowed to fragment across the CCS.
|
||||||
|
if c.rawConn.Hand.Len() > 0 {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
// In TLS 1.3, change_cipher_spec records are ignored until the
|
||||||
|
// Finished. See RFC 8446, Appendix D.4. Note that according to Section
|
||||||
|
// 5, a server can send a ChangeCipherSpec before its ServerHello, when
|
||||||
|
// c.vers is still unset. That's not useful though and suspicious if the
|
||||||
|
// server then selects a lower protocol version, so don't allow that.
|
||||||
|
if *c.rawConn.Vers == tls.VersionTLS13 {
|
||||||
|
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
||||||
|
}
|
||||||
|
// if !expectChangeCipherSpec {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
//}
|
||||||
|
//if err := c.rawConn.In.changeCipherSpec(); err != nil {
|
||||||
|
// return c.rawConn.In.setErrorLocked(c.sendAlert(err.(alert)))
|
||||||
|
//}
|
||||||
|
|
||||||
|
case recordTypeApplicationData:
|
||||||
|
// Some OpenSSL servers send empty records in order to randomize the
|
||||||
|
// CBC RawIV. Ignore a limited number of empty records.
|
||||||
|
if len(data) == 0 {
|
||||||
|
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
||||||
|
}
|
||||||
|
// Note that data is owned by c.rawInput, following the Next call above,
|
||||||
|
// to avoid copying the plaintext. This is safe because c.rawInput is
|
||||||
|
// not read from or written to until c.input is drained.
|
||||||
|
c.rawConn.Input.Reset(data)
|
||||||
|
case recordTypeHandshake:
|
||||||
|
if len(data) == 0 {
|
||||||
|
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||||
|
}
|
||||||
|
c.rawConn.Hand.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:staticcheck
|
||||||
|
func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {
|
||||||
|
// Read from kernel.
|
||||||
|
if c.kernelRx {
|
||||||
|
return c.readKernelRecord()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header, payload.
|
||||||
|
if err = c.readFromUntil(c.conn, recordHeaderLen); err != nil {
|
||||||
|
// RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify
|
||||||
|
// is an error, but popular web sites seem to do this, so we accept it
|
||||||
|
// if and only if at the record boundary.
|
||||||
|
if err == io.ErrUnexpectedEOF && c.rawConn.RawInput.Len() == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
if e, ok := err.(net.Error); !ok || !e.Temporary() {
|
||||||
|
c.rawConn.In.SetErrorLocked(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hdr := c.rawConn.RawInput.Bytes()[:recordHeaderLen]
|
||||||
|
typ = hdr[0]
|
||||||
|
|
||||||
|
vers := uint16(hdr[1])<<8 | uint16(hdr[2])
|
||||||
|
expectedVers := *c.rawConn.Vers
|
||||||
|
if expectedVers == tls.VersionTLS13 {
|
||||||
|
// All TLS 1.3 records are expected to have 0x0303 (1.2) after
|
||||||
|
// the initial hello (RFC 8446 Section 5.1).
|
||||||
|
expectedVers = tls.VersionTLS12
|
||||||
|
}
|
||||||
|
n := int(hdr[3])<<8 | int(hdr[4])
|
||||||
|
if /*c.haveVers && */ vers != expectedVers {
|
||||||
|
c.sendAlert(alertProtocolVersion)
|
||||||
|
msg := fmt.Sprintf("received record with version %x when expecting version %x", vers, expectedVers)
|
||||||
|
err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//if !c.haveVers {
|
||||||
|
// // First message, be extra suspicious: this might not be a TLS
|
||||||
|
// // client. Bail out before reading a full 'body', if possible.
|
||||||
|
// // The current max version is 3.3 so if the version is >= 16.0,
|
||||||
|
// // it's probably not real.
|
||||||
|
// if (typ != recordTypeAlert && typ != recordTypeHandshake) || vers >= 0x1000 {
|
||||||
|
// err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(c.conn, "first record does not look like a TLS handshake"))
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
if *c.rawConn.Vers == tls.VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext {
|
||||||
|
c.sendAlert(alertRecordOverflow)
|
||||||
|
msg := fmt.Sprintf("oversized record received with length %d", n)
|
||||||
|
err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = c.readFromUntil(c.conn, recordHeaderLen+n); err != nil {
|
||||||
|
if e, ok := err.(net.Error); !ok || !e.Temporary() {
|
||||||
|
c.rawConn.In.SetErrorLocked(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process message.
|
||||||
|
record := c.rawConn.RawInput.Next(recordHeaderLen + n)
|
||||||
|
data, typ, err = c.rawConn.In.Decrypt(record)
|
||||||
|
if err != nil {
|
||||||
|
err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError))))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like
|
||||||
|
// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3.
|
||||||
|
func (c *Conn) retryReadRecord( /*expectChangeCipherSpec bool*/ ) error {
|
||||||
|
//c.retryCount++
|
||||||
|
//if c.retryCount > maxUselessRecords {
|
||||||
|
// c.sendAlert(alertUnexpectedMessage)
|
||||||
|
// return c.in.setErrorLocked(errors.New("tls: too many ignored records"))
|
||||||
|
//}
|
||||||
|
return c.readRecord( /*expectChangeCipherSpec*/ )
|
||||||
|
}
|
||||||
|
|
||||||
|
// atLeastReader reads from R, stopping with EOF once at least N bytes have been
|
||||||
|
// read. It is different from an io.LimitedReader in that it doesn't cut short
|
||||||
|
// the last Read call, and in that it considers an early EOF an error.
|
||||||
|
type atLeastReader struct {
|
||||||
|
R io.Reader
|
||||||
|
N int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *atLeastReader) Read(p []byte) (int, error) {
|
||||||
|
if r.N <= 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := r.R.Read(p)
|
||||||
|
r.N -= int64(n) // won't underflow unless len(p) >= n > 9223372036854775809
|
||||||
|
if r.N > 0 && err == io.EOF {
|
||||||
|
return n, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if r.N <= 0 && err == nil {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFromUntil reads from r into c.rawConn.RawInput until c.rawConn.RawInput contains
|
||||||
|
// at least n bytes or else returns an error.
|
||||||
|
func (c *Conn) readFromUntil(r io.Reader, n int) error {
|
||||||
|
if c.rawConn.RawInput.Len() >= n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
needs := n - c.rawConn.RawInput.Len()
|
||||||
|
// There might be extra input waiting on the wire. Make a best effort
|
||||||
|
// attempt to fetch it so that it can be used in (*Conn).Read to
|
||||||
|
// "predict" closeNotify alerts.
|
||||||
|
c.rawConn.RawInput.Grow(needs + bytes.MinRead)
|
||||||
|
_, err := c.rawConn.RawInput.ReadFrom(&atLeastReader{r, int64(needs)})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err tls.RecordHeaderError) {
|
||||||
|
err.Msg = msg
|
||||||
|
err.Conn = conn
|
||||||
|
copy(err.RecordHeader[:], c.rawConn.RawInput.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user