mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
684 Commits
v1.9.0-rc.
...
dev-wifi-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
766972ce52 | ||
|
|
f1e48a1cad | ||
|
|
ba496ae300 | ||
|
|
4488148322 | ||
|
|
f086454f81 | ||
|
|
f9b6a068ee | ||
|
|
0ad2a441d9 | ||
|
|
c714b59c87 | ||
|
|
bd6b125707 | ||
|
|
d1109cee90 | ||
|
|
b48002b4db | ||
|
|
67cedfd927 | ||
|
|
853b576d12 | ||
|
|
05cd7f6192 | ||
|
|
e4cf55a86d | ||
|
|
2bf605fad4 | ||
|
|
b913899d43 | ||
|
|
e1a22c0cc2 | ||
|
|
d890c8e8d7 | ||
|
|
1b4ffff67c | ||
|
|
6d012c04cd | ||
|
|
c1309b63c9 | ||
|
|
6674b252bf | ||
|
|
8c7b2e4ac3 | ||
|
|
f400c927c4 | ||
|
|
587d330b58 | ||
|
|
719a28920a | ||
|
|
2e0d344a3d | ||
|
|
387084b7c7 | ||
|
|
e15c02ee33 | ||
|
|
2648b80c6b | ||
|
|
391f7a73d2 | ||
|
|
c1565e2bb0 | ||
|
|
605d8b41f9 | ||
|
|
03d8243d41 | ||
|
|
30f1beb071 | ||
|
|
3841d9cb5a | ||
|
|
dbb7345ec9 | ||
|
|
88f000412b | ||
|
|
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 | ||
|
|
f56131f38e | ||
|
|
273a11d550 | ||
|
|
ae8ce75e41 | ||
|
|
d6d94b689f | ||
|
|
30d785f1ee | ||
|
|
db5ec3cdfc | ||
|
|
9aca54d039 | ||
|
|
d55d5009c2 | ||
|
|
4f3ee61104 | ||
|
|
96eb98c00a | ||
|
|
68ce9577c6 | ||
|
|
3ae036e997 | ||
|
|
5da2d1d470 | ||
|
|
8e2baf40f1 | ||
|
|
c24c40dfee | ||
|
|
32e52ce1ed | ||
|
|
ed46438359 | ||
|
|
0b5490d5a3 | ||
|
|
2d73ef511d | ||
|
|
63e6c85f6f | ||
|
|
8946a6d2d0 | ||
|
|
d3132645fb | ||
|
|
373f158fe0 | ||
|
|
ce36835fab | ||
|
|
619fa671d7 | ||
|
|
eb07c7a79e | ||
|
|
7eb3535094 | ||
|
|
93b68312cf | ||
|
|
97ce666e43 | ||
|
|
4000e1e66d | ||
|
|
270740e859 | ||
|
|
6cad142cfe | ||
|
|
093013687c | ||
|
|
ff31c469a0 | ||
|
|
fbe390268c | ||
|
|
07ac01dcb7 | ||
|
|
badfdb62cd | ||
|
|
986a410b30 | ||
|
|
9db2d58545 | ||
|
|
4eed46ac59 | ||
|
|
abc38d1dab | ||
|
|
8d6c4f1289 | ||
|
|
a2d40eb8b8 | ||
|
|
17b502bb4b | ||
|
|
a0d4421085 | ||
|
|
0d443072d1 | ||
|
|
c9fb99b799 | ||
|
|
92d245ad04 | ||
|
|
0908627297 | ||
|
|
7f79458b4f | ||
|
|
9b4c11ba95 | ||
|
|
27c31eac5d | ||
|
|
bab8dc0b82 | ||
|
|
d09d2fb665 | ||
|
|
e64cf3b7df | ||
|
|
9b73222314 | ||
|
|
3923b57abf | ||
|
|
4807e64609 | ||
|
|
eeb37d89f1 | ||
|
|
08c1ec4b7e | ||
|
|
6b4cf67add | ||
|
|
e65926fd08 | ||
|
|
f2ec319fe1 | ||
|
|
32377a61b7 | ||
|
|
7aac801ccd | ||
|
|
96fdf59ee4 | ||
|
|
50b8f3ab94 | ||
|
|
ff7aaf977b | ||
|
|
9a1efbe54d | ||
|
|
906c21f458 | ||
|
|
d5e7af7a7e | ||
|
|
4d41f03bd5 | ||
|
|
30704a15a7 | ||
|
|
83889178ed | ||
|
|
1d2720bf5e | ||
|
|
c4b6d0eadb | ||
|
|
0c66888691 | ||
|
|
68781387fe | ||
|
|
fd299a0961 | ||
|
|
285a82050c | ||
|
|
2dbb8c55c9 | ||
|
|
effcf39469 | ||
|
|
9db9484863 | ||
|
|
ca813f461b | ||
|
|
bb46cdb2b3 | ||
|
|
dcb10c21a1 | ||
|
|
05ea0ca00e | ||
|
|
c098f282b1 | ||
|
|
ecf82d197c | ||
|
|
9afe75586a | ||
|
|
a1be455202 | ||
|
|
19fb214226 | ||
|
|
28ec898a8c | ||
|
|
467b1bbeeb | ||
|
|
02ab8ce806 | ||
|
|
ce69e620e9 | ||
|
|
1133cf3ef5 | ||
|
|
59a607e303 | ||
|
|
313be3d7a4 | ||
|
|
4fe40fcee0 | ||
|
|
e233fd4fe5 | ||
|
|
9f7683818f | ||
|
|
179e3cb2f5 | ||
|
|
41b960552d | ||
|
|
8304295c48 | ||
|
|
253b41936e | ||
|
|
ce5b4b06b5 | ||
|
|
50f5006c43 | ||
|
|
e42ff22c2e | ||
|
|
578571b972 | ||
|
|
935beca45d | ||
|
|
3e246f1173 | ||
|
|
1bc27a32c2 | ||
|
|
bc2e3960e4 | ||
|
|
9c4ab0bf33 | ||
|
|
27bdef34c7 | ||
|
|
3c00099ed4 | ||
|
|
2babf07f9a | ||
|
|
4795ed712b | ||
|
|
d4cd564dbe | ||
|
|
1676e13d3e | ||
|
|
50576084c6 | ||
|
|
3a94e792a2 | ||
|
|
9f69f41f68 | ||
|
|
e6847ff50e | ||
|
|
2ac2589d14 | ||
|
|
64a94e8144 | ||
|
|
3ed8a5c5d1 | ||
|
|
0a922c6fe3 | ||
|
|
52f3a4226c | ||
|
|
483d9fa503 | ||
|
|
dd9de694f8 | ||
|
|
5cdf5c1d9e | ||
|
|
cec7e47086 | ||
|
|
1e6a3f1f0b | ||
|
|
f0b6818b4c | ||
|
|
3032317918 | ||
|
|
db22f61846 | ||
|
|
8c3a98faa2 | ||
|
|
1e787cb607 | ||
|
|
558585b01d | ||
|
|
6e7ecbd4f5 | ||
|
|
5a661cde67 | ||
|
|
3cc0e87cfb | ||
|
|
effea5a2b3 | ||
|
|
7f168c5ec6 | ||
|
|
0e9129ee3f | ||
|
|
1086d5e665 | ||
|
|
d9102ba599 | ||
|
|
17019f1729 | ||
|
|
6be07ed51f | ||
|
|
af58e3bec0 | ||
|
|
e58b549d0f | ||
|
|
1d81996ceb | ||
|
|
97c47e72c4 | ||
|
|
122be275b0 | ||
|
|
0bb1132034 | ||
|
|
de14337b4b | ||
|
|
1e07633914 | ||
|
|
e3e203844e | ||
|
|
84a102a6ef | ||
|
|
f1c76c4dde | ||
|
|
8df0aa5719 | ||
|
|
21faadb992 | ||
|
|
88099a304a | ||
|
|
f504fb0d46 | ||
|
|
1d517b6ca5 | ||
|
|
b702d0b67a | ||
|
|
a001e30d8b | ||
|
|
cdb93f0bb2 | ||
|
|
718cffea9a | ||
|
|
9585c53e9f | ||
|
|
d66d5cd457 | ||
|
|
8c143feec8 | ||
|
|
419058f466 | ||
|
|
1a6047a61b | ||
|
|
327bb35ddd | ||
|
|
6ed9a06394 | ||
|
|
b80ec55ba0 | ||
|
|
08718112ae | ||
|
|
956ee361df | ||
|
|
e93d0408be | ||
|
|
137832ff3e | ||
|
|
3ede29fb6d | ||
|
|
82ab68b542 | ||
|
|
e55723d84d | ||
|
|
2f4d2d97f9 | ||
|
|
926d6f769e | ||
|
|
846777cd0c | ||
|
|
06533b7a3b | ||
|
|
4a95558c53 | ||
|
|
e39a28ed5a | ||
|
|
b2c708a3e6 | ||
|
|
a9209bb3e5 | ||
|
|
9dc3bb975a | ||
|
|
3a7acaa92a | ||
|
|
6bebe2483b | ||
|
|
93cf134995 | ||
|
|
ff7d8c9ba8 | ||
|
|
50f07b42f6 | ||
|
|
db3a0c636d | ||
|
|
fec38f85cd | ||
|
|
dcb0141646 | ||
|
|
f4f5a3c925 | ||
|
|
9b8d6c1b73 | ||
|
|
2f776168de | ||
|
|
923d3222b0 | ||
|
|
bda93d516b | ||
|
|
7eec3fb57a | ||
|
|
b1d75812c5 | ||
|
|
d44e7d9834 | ||
|
|
369bc7cea3 | ||
|
|
4b7a83da16 | ||
|
|
0f7154afbd | ||
|
|
a06d10c3bc | ||
|
|
63cc6cc76c | ||
|
|
d55c5b5cab | ||
|
|
b624c2dcc7 | ||
|
|
9415444ebd | ||
|
|
95606191d8 | ||
|
|
e586d9e9bc | ||
|
|
8c7eaa4477 | ||
|
|
8464c8cb7c | ||
|
|
39d7127651 | ||
|
|
e2077009c4 | ||
|
|
700a8eb425 | ||
|
|
3b0cba0852 | ||
|
|
f5554dd8b8 | ||
|
|
4d0362d530 | ||
|
|
97ccd2ca04 | ||
|
|
1ed6654ad4 | ||
|
|
5385f75f53 | ||
|
|
ad97d4e11f | ||
|
|
09d4e91b77 | ||
|
|
3dbdda9555 | ||
|
|
1f4ed6ff8f | ||
|
|
6ddbe19bc0 | ||
|
|
d7205ecc60 | ||
|
|
9e243e0ff9 | ||
|
|
02bc3e0a0a | ||
|
|
87be6dc235 | ||
|
|
c1c30976dc | ||
|
|
9bac18bcd1 | ||
|
|
ceda5cc95d | ||
|
|
27d6b63e71 | ||
|
|
b57abcc73c | ||
|
|
f1147965dd | ||
|
|
45f3234c73 | ||
|
|
aae3fded32 | ||
|
|
090494faf5 | ||
|
|
db5719e22f | ||
|
|
064fb9b873 | ||
|
|
f6a1e123fc | ||
|
|
3066dfe3b3 | ||
|
|
1128fdd8c7 | ||
|
|
cfd9879b17 | ||
|
|
9ceb660c57 | ||
|
|
7d00d7df28 | ||
|
|
21b1ac26b9 | ||
|
|
7fec8d842e | ||
|
|
07c678fb85 | ||
|
|
baecfc7778 | ||
|
|
07de36ecdb | ||
|
|
2c8a8303cd | ||
|
|
e5991cae0b | ||
|
|
1349acfd5a | ||
|
|
98ff897f35 | ||
|
|
6144c8e340 | ||
|
|
c8caac9f67 | ||
|
|
81e9eda357 | ||
|
|
7cba3da108 | ||
|
|
82d06b43e7 | ||
|
|
a7ac91f573 | ||
|
|
0540a95a43 | ||
|
|
94707dfcdd | ||
|
|
8a17043502 | ||
|
|
b0aaa86806 | ||
|
|
8a2d3fbb28 | ||
|
|
4652019608 | ||
|
|
06fa5abf63 | ||
|
|
996fbbf0c3 | ||
|
|
142ff1b455 | ||
|
|
74d662f7a3 | ||
|
|
085f603377 | ||
|
|
460fae83dc | ||
|
|
bb9bd9bff6 | ||
|
|
c2354ebf25 | ||
|
|
c1f4755c4e | ||
|
|
0ca5909b06 | ||
|
|
e77a8114c5 | ||
|
|
f1393235ff | ||
|
|
bdba2365de | ||
|
|
ce0da5b557 | ||
|
|
3853201412 | ||
|
|
7003ef40a3 | ||
|
|
59ec92228c | ||
|
|
0eeb2da323 | ||
|
|
977b0fac02 | ||
|
|
51964801ff | ||
|
|
e08c052fc9 | ||
|
|
53927d8bbd | ||
|
|
968b9bc217 | ||
|
|
69dc87aa6d | ||
|
|
4193df375f | ||
|
|
5ff7006326 | ||
|
|
a89107ea9d | ||
|
|
9ffdbba2ed | ||
|
|
65c71049ea | ||
|
|
7d4e6a7f4e | ||
|
|
d612620c5d | ||
|
|
8a9a77a438 | ||
|
|
a2098c18e1 | ||
|
|
cf2181dd3a | ||
|
|
5899e95ff1 | ||
|
|
d7160c19cf | ||
|
|
da9e22b4e6 | ||
|
|
0e120f8a44 | ||
|
|
d918863ac5 | ||
|
|
2ae192305c | ||
|
|
71d1879bd6 | ||
|
|
917514e09f | ||
|
|
5327aeaea4 | ||
|
|
93ae3f7a1e | ||
|
|
f24a2aed7d | ||
|
|
0517ceef76 | ||
|
|
830ea46932 | ||
|
|
cd0fcd5ddc | ||
|
|
003176f069 | ||
|
|
71d92518c1 | ||
|
|
b5dcd6bf59 | ||
|
|
11c7b4a866 | ||
|
|
ee14135298 | ||
|
|
cbcf005f37 | ||
|
|
daee0b154e | ||
|
|
d530c724c0 | ||
|
|
7f698c1104 |
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
|
||||||
25
.fpm_systemd
Normal file
25
.fpm_systemd
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-s dir
|
||||||
|
--name sing-box
|
||||||
|
--category net
|
||||||
|
--license GPL-3.0-or-later
|
||||||
|
--description "The universal proxy platform."
|
||||||
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
|
--no-deb-generate-changes
|
||||||
|
--config-files /etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||||
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
|
||||||
27
.github/setup_go_for_windows7.sh
vendored
Executable file
27
.github/setup_go_for_windows7.sh
vendored
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION="1.25.1"
|
||||||
|
|
||||||
|
mkdir -p $HOME/go
|
||||||
|
cd $HOME/go
|
||||||
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
mv go go_win7
|
||||||
|
cd go_win7
|
||||||
|
|
||||||
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
|
# this patch file only works on golang1.25.x
|
||||||
|
# that means after golang1.26 release it must be changed
|
||||||
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||||
|
# revert:
|
||||||
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
|
||||||
|
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||||
|
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.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
|
||||||
666
.github/workflows/build.yml
vendored
Normal file
666
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,666 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
build:
|
||||||
|
description: "Build type"
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
default: "All"
|
||||||
|
options:
|
||||||
|
- All
|
||||||
|
- Binary
|
||||||
|
- Android
|
||||||
|
- Apple
|
||||||
|
- app-store
|
||||||
|
- iOS
|
||||||
|
- macOS
|
||||||
|
- tvOS
|
||||||
|
- macOS-standalone
|
||||||
|
- publish-android
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main-next
|
||||||
|
- dev-next
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
calculate_version:
|
||||||
|
name: Calculate version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Check input version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
echo "version=${{ inputs.version }}"
|
||||||
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Calculate version
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
|
- name: Set outputs
|
||||||
|
id: outputs
|
||||||
|
run: |-
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
build:
|
||||||
|
name: Build binary
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
|
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
|
- { os: windows, arch: amd64 }
|
||||||
|
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
||||||
|
- { os: windows, arch: "386" }
|
||||||
|
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
||||||
|
- { os: windows, arch: arm64 }
|
||||||
|
|
||||||
|
- { os: darwin, arch: amd64 }
|
||||||
|
- { os: darwin, arch: arm64 }
|
||||||
|
- { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
||||||
|
|
||||||
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Setup Go 1.24
|
||||||
|
if: matrix.legacy_go124
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.24.6
|
||||||
|
- name: Cache Go for Windows 7
|
||||||
|
if: matrix.legacy_win7
|
||||||
|
id: cache-go-for-windows7
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/go_win7
|
||||||
|
key: go_win7_1251
|
||||||
|
- name: Setup Go for Windows 7
|
||||||
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
|
run: |-
|
||||||
|
.github/setup_go_for_windows7.sh
|
||||||
|
- name: Setup Go for Windows 7
|
||||||
|
if: matrix.legacy_win7
|
||||||
|
run: |-
|
||||||
|
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
||||||
|
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: matrix.os == 'android'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
local-cache: true
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build
|
||||||
|
if: matrix.os != 'android'
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -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 }}
|
||||||
|
- name: Build Android
|
||||||
|
if: matrix.os == 'android'
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
go install -v ./cmd/internal/build
|
||||||
|
export CC='${{ matrix.ndk }}-clang'
|
||||||
|
export CXX="${CC}++"
|
||||||
|
mkdir -p dist
|
||||||
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
BUILD_GOOS: ${{ matrix.os }}
|
||||||
|
BUILD_GOARCH: ${{ matrix.arch }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Set name
|
||||||
|
run: |-
|
||||||
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||||
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||||
|
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
|
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||||
|
fi
|
||||||
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
|
- name: Package RPM
|
||||||
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Package Pacman
|
||||||
|
if: matrix.pacman != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libarchive-tools
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t pacman \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
|
--architecture ${{ matrix.pacman }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
- name: Package OpenWrt
|
||||||
|
if: matrix.openwrt != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_openwrt .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/openwrt.deb" \
|
||||||
|
--architecture all \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||||
|
done
|
||||||
|
rm "dist/openwrt.deb"
|
||||||
|
- name: Archive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd dist
|
||||||
|
mkdir -p "${DIR_NAME}"
|
||||||
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
|
if [ '${{ matrix.os }}' = 'windows' ]; then
|
||||||
|
cp sing-box "${DIR_NAME}/sing-box.exe"
|
||||||
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
|
else
|
||||||
|
cp sing-box "${DIR_NAME}"
|
||||||
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
|
fi
|
||||||
|
rm -r "${DIR_NAME}"
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
||||||
|
path: "dist"
|
||||||
|
build_android:
|
||||||
|
name: Build Android
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Setup Android NDK
|
||||||
|
id: setup-ndk
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
- name: Setup OpenJDK
|
||||||
|
run: |-
|
||||||
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build library
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
make lib_android
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout dev
|
||||||
|
- name: Gradle cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.gradle
|
||||||
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Update version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
|
- name: Update nightly version
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci --nightly
|
||||||
|
- name: Build
|
||||||
|
run: |-
|
||||||
|
mkdir clients/android/app/libs
|
||||||
|
cp libbox.aar clients/android/app/libs
|
||||||
|
cd clients/android
|
||||||
|
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
|
- name: Prepare upload
|
||||||
|
run: |-
|
||||||
|
mkdir -p dist
|
||||||
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
||||||
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-android-apks
|
||||||
|
path: 'dist'
|
||||||
|
publish_android:
|
||||||
|
name: Publish Android
|
||||||
|
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Setup Android NDK
|
||||||
|
id: setup-ndk
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
- name: Setup OpenJDK
|
||||||
|
run: |-
|
||||||
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build library
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
make lib_android
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout dev
|
||||||
|
- name: Gradle cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.gradle
|
||||||
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
|
mkdir clients/android/app/libs
|
||||||
|
cp libbox.aar clients/android/app/libs
|
||||||
|
cd clients/android
|
||||||
|
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||||
|
./gradlew :app:publishPlayReleaseBundle
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
|
build_apple:
|
||||||
|
name: Build Apple clients
|
||||||
|
runs-on: macos-26
|
||||||
|
if: false
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: iOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
||||||
|
platform: ios
|
||||||
|
scheme: SFI
|
||||||
|
destination: 'generic/platform=iOS'
|
||||||
|
archive: build/SFI.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: macOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
||||||
|
platform: macos
|
||||||
|
scheme: SFM
|
||||||
|
destination: 'generic/platform=macOS'
|
||||||
|
archive: build/SFM.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: tvOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
||||||
|
platform: tvos
|
||||||
|
scheme: SFT
|
||||||
|
destination: 'generic/platform=tvOS'
|
||||||
|
archive: build/SFT.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: macOS-standalone
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
||||||
|
platform: macos
|
||||||
|
scheme: SFM.System
|
||||||
|
destination: 'generic/platform=macOS'
|
||||||
|
archive: build/SFM.System.xcarchive
|
||||||
|
export: SFM.System/Export.plist
|
||||||
|
export_path: build/SFM.System
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
if: matrix.if
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
if: matrix.if
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Set tag
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/apple
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/apple
|
||||||
|
git checkout dev
|
||||||
|
- name: Setup certificates
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
|
||||||
|
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
|
||||||
|
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||||
|
|
||||||
|
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||||
|
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||||
|
|
||||||
|
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||||
|
mkdir -p "$PROFILES_PATH"
|
||||||
|
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||||
|
|
||||||
|
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||||
|
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||||
|
|
||||||
|
xcrun notarytool store-credentials "notarytool-password" \
|
||||||
|
--key $ASC_KEY_PATH \
|
||||||
|
--key-id $ASC_KEY_ID \
|
||||||
|
--issuer $ASC_KEY_ISSUER_ID
|
||||||
|
|
||||||
|
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||||
|
env:
|
||||||
|
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
||||||
|
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||||
|
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
|
||||||
|
ASC_KEY: ${{ secrets.ASC_KEY }}
|
||||||
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
||||||
|
- name: Build library
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
|
||||||
|
mv Libbox.xcframework clients/apple
|
||||||
|
- name: Update macOS version
|
||||||
|
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
|
- name: Update version
|
||||||
|
if: matrix.if && matrix.name != 'iOS'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
|
- name: Build
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
cd clients/apple
|
||||||
|
xcodebuild archive \
|
||||||
|
-scheme "${{ matrix.scheme }}" \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "${{ matrix.destination }}" \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
|
- name: Upload to App Store Connect
|
||||||
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
|
||||||
|
cd clients/apple
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-exportOptionsPlist ${{ matrix.upload }} \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
|
- name: Publish to TestFlight
|
||||||
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||||
|
- name: Build image
|
||||||
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
pushd clients/apple
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-exportOptionsPlist ${{ matrix.export }} \
|
||||||
|
-exportPath "${{ matrix.export_path }}"
|
||||||
|
brew install create-dmg
|
||||||
|
create-dmg \
|
||||||
|
--volname "sing-box" \
|
||||||
|
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||||
|
--icon "SFM.app" 0 0 \
|
||||||
|
--hide-extension "SFM.app" \
|
||||||
|
--app-drop-link 0 0 \
|
||||||
|
--skip-jenkins \
|
||||||
|
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||||
|
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||||
|
cd "${{ matrix.archive }}"
|
||||||
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
|
popd
|
||||||
|
|
||||||
|
mkdir -p dist
|
||||||
|
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
||||||
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
- name: Upload image
|
||||||
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-macos-dmg
|
||||||
|
path: 'dist'
|
||||||
|
upload:
|
||||||
|
name: Upload builds
|
||||||
|
if: "!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
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
- build
|
||||||
|
- build_android
|
||||||
|
- build_apple
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Cache ghr
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-ghr
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/bin/ghr
|
||||||
|
key: ghr
|
||||||
|
- name: Setup ghr
|
||||||
|
if: steps.cache-ghr.outputs.cache-hit != 'true'
|
||||||
|
run: |-
|
||||||
|
cd $HOME
|
||||||
|
git clone https://github.com/nekohasekai/ghr ghr
|
||||||
|
cd ghr
|
||||||
|
go install -v .
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Download builds
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Upload builds
|
||||||
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
|
run: |-
|
||||||
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Replace builds
|
||||||
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
|
run: |-
|
||||||
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
|
ghr --replace -p 5 "v${VERSION}" dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
220
.github/workflows/debug.yml
vendored
220
.github/workflows/debug.yml
vendored
@@ -1,220 +0,0 @@
|
|||||||
name: Debug build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- stable-next
|
|
||||||
- main-next
|
|
||||||
- dev-next
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- '.github/**'
|
|
||||||
- '!.github/workflows/debug.yml'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- stable-next
|
|
||||||
- main-next
|
|
||||||
- dev-next
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Debug build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.22
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Run Test
|
|
||||||
run: |
|
|
||||||
go test -v ./...
|
|
||||||
build_go118:
|
|
||||||
name: Debug build (Go 1.18)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.18
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go118-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build_go118
|
|
||||||
build_go120:
|
|
||||||
name: Debug build (Go 1.20)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.20
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go120-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build_go120
|
|
||||||
build_go121:
|
|
||||||
name: Debug build (Go 1.21)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.21
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go121-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build
|
|
||||||
cross:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# windows
|
|
||||||
- name: windows-amd64
|
|
||||||
goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: windows-amd64-v3
|
|
||||||
goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: windows-386
|
|
||||||
goos: windows
|
|
||||||
goarch: 386
|
|
||||||
- name: windows-arm64
|
|
||||||
goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- name: windows-arm32v7
|
|
||||||
goos: windows
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
|
|
||||||
# linux
|
|
||||||
- name: linux-amd64
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: linux-amd64-v3
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: linux-386
|
|
||||||
goos: linux
|
|
||||||
goarch: 386
|
|
||||||
- name: linux-arm64
|
|
||||||
goos: linux
|
|
||||||
goarch: arm64
|
|
||||||
- name: linux-armv5
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 5
|
|
||||||
- name: linux-armv6
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 6
|
|
||||||
- name: linux-armv7
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
- name: linux-mips-softfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mips
|
|
||||||
gomips: softfloat
|
|
||||||
- name: linux-mips-hardfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mips
|
|
||||||
gomips: hardfloat
|
|
||||||
- name: linux-mipsel-softfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mipsle
|
|
||||||
gomips: softfloat
|
|
||||||
- name: linux-mipsel-hardfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mipsle
|
|
||||||
gomips: hardfloat
|
|
||||||
- name: linux-mips64
|
|
||||||
goos: linux
|
|
||||||
goarch: mips64
|
|
||||||
- name: linux-mips64el
|
|
||||||
goos: linux
|
|
||||||
goarch: mips64le
|
|
||||||
- name: linux-s390x
|
|
||||||
goos: linux
|
|
||||||
goarch: s390x
|
|
||||||
# darwin
|
|
||||||
- name: darwin-amd64
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: darwin-amd64-v3
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: darwin-arm64
|
|
||||||
goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
# freebsd
|
|
||||||
- name: freebsd-amd64
|
|
||||||
goos: freebsd
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: freebsd-amd64-v3
|
|
||||||
goos: freebsd
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: freebsd-386
|
|
||||||
goos: freebsd
|
|
||||||
goarch: 386
|
|
||||||
- name: freebsd-arm64
|
|
||||||
goos: freebsd
|
|
||||||
goarch: arm64
|
|
||||||
fail-fast: true
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GOOS: ${{ matrix.goos }}
|
|
||||||
GOARCH: ${{ matrix.goarch }}
|
|
||||||
GOAMD64: ${{ matrix.goamd64 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
TAGS: with_clash_api,with_quic
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.21
|
|
||||||
- name: Build
|
|
||||||
id: build
|
|
||||||
run: make
|
|
||||||
121
.github/workflows/docker.yml
vendored
121
.github/workflows/docker.yml
vendored
@@ -1,16 +1,93 @@
|
|||||||
name: Build Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- released
|
- published
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: "The tag version you want to build"
|
description: "The tag version you want to build"
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm/v6
|
||||||
|
- linux/arm/v7
|
||||||
|
- linux/arm64
|
||||||
|
- linux/386
|
||||||
|
- linux/ppc64le
|
||||||
|
- linux/riscv64
|
||||||
|
- linux/s390x
|
||||||
|
steps:
|
||||||
|
- name: Get commit to build
|
||||||
|
id: ref
|
||||||
|
run: |-
|
||||||
|
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||||
|
ref="${{ github.ref_name }}"
|
||||||
|
else
|
||||||
|
ref="${{ github.event.inputs.tag }}"
|
||||||
|
fi
|
||||||
|
echo "ref=$ref"
|
||||||
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY_IMAGE }}
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
context: .
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
merge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -29,34 +106,28 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "latest=$latest"
|
echo "latest=$latest"
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Download digests
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
path: /tmp/digests
|
||||||
- name: Setup Docker Buildx
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Setup QEMU for Docker Buildx
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Create manifest list and push
|
||||||
id: metadata
|
working-directory: /tmp/digests
|
||||||
uses: docker/metadata-action@v5
|
run: |
|
||||||
with:
|
docker buildx imagetools create \
|
||||||
images: ghcr.io/sagernet/sing-box
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||||
- name: Build and release Docker images
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
uses: docker/build-push-action@v5
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
with:
|
- name: Inspect image
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
run: |
|
||||||
context: .
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
target: dist
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
build-args: |
|
|
||||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
tags: |
|
|
||||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }}
|
|
||||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }}
|
|
||||||
push: true
|
|
||||||
|
|||||||
11
.github/workflows/lint.yml
vendored
11
.github/workflows/lint.yml
vendored
@@ -22,16 +22,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # 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.22
|
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
|
||||||
|
|||||||
190
.github/workflows/linux.yml
vendored
190
.github/workflows/linux.yml
vendored
@@ -1,39 +1,189 @@
|
|||||||
name: Release to Linux repository
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
forceBeta:
|
||||||
|
description: "Force beta"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
calculate_version:
|
||||||
|
name: Calculate version
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # 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.22
|
go-version: ^1.25.1
|
||||||
- name: Extract signing key
|
- name: Check input version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p $HOME/.gnupg
|
echo "version=${{ inputs.version }}"
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
${{ secrets.GPG_KEY }}
|
- name: Calculate version
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
if: github.event_name != 'workflow_dispatch'
|
||||||
EOF
|
run: |-
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
- name: Publish release
|
- name: Set outputs
|
||||||
uses: goreleaser/goreleaser-action@v5
|
id: outputs
|
||||||
|
run: |-
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
build:
|
||||||
|
name: Build binary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
fetch-depth: 0
|
||||||
version: latest
|
- name: Setup Go
|
||||||
args: release -f .goreleaser.fury.yaml --clean
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.25.1
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: matrix.os == 'android'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
local-cache: true
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
|
||||||
|
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:
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: ${{ matrix.os }}
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
- name: Set mtime
|
||||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
run: |-
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
- name: Set name
|
||||||
|
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
|
- name: Set beta name
|
||||||
|
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
|
- name: Set version
|
||||||
|
run: |-
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
|
- name: Package RPM
|
||||||
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
|
path: "dist"
|
||||||
|
upload:
|
||||||
|
name: Upload builds
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
- build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Download builds
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish packages
|
||||||
|
run: |-
|
||||||
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
|
|||||||
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@@ -12,4 +12,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 5
|
days-before-close: 5
|
||||||
|
exempt-issue-labels: 'bug,enhancement'
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,3 +14,7 @@
|
|||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
|
/venv/
|
||||||
|
CLAUDE.md
|
||||||
|
AGENTS.md
|
||||||
|
/.claude/
|
||||||
|
|||||||
@@ -1,26 +1,59 @@
|
|||||||
linters:
|
version: "2"
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- gofumpt
|
|
||||||
- govet
|
|
||||||
- gci
|
|
||||||
- staticcheck
|
|
||||||
- paralleltest
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
skip-dirs:
|
go: "1.25"
|
||||||
- transport/simple-obfs
|
build-tags:
|
||||||
- transport/clashssr
|
- with_gvisor
|
||||||
- transport/cloudflaretls
|
- with_quic
|
||||||
- transport/shadowtls/tls
|
- with_dhcp
|
||||||
- transport/shadowtls/tls_go119
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
linters-settings:
|
- with_acme
|
||||||
gci:
|
- with_clash_api
|
||||||
custom-order: true
|
linters:
|
||||||
sections:
|
default: none
|
||||||
- standard
|
enable:
|
||||||
- prefix(github.com/sagernet/)
|
- govet
|
||||||
- default
|
- ineffassign
|
||||||
staticcheck:
|
- paralleltest
|
||||||
go: '1.20'
|
- 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$
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_ech
|
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
@@ -26,6 +27,7 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
@@ -36,7 +38,6 @@ nfpms:
|
|||||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
builds:
|
builds:
|
||||||
- main
|
- main
|
||||||
vendor: sagernet
|
|
||||||
homepage: https://sing-box.sagernet.org/
|
homepage: https://sing-box.sagernet.org/
|
||||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
description: The universal proxy platform.
|
description: The universal proxy platform.
|
||||||
@@ -48,11 +49,26 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: 2
|
||||||
project_name: sing-box
|
project_name: sing-box
|
||||||
builds:
|
builds:
|
||||||
- &template
|
- &template
|
||||||
@@ -7,29 +8,31 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_ech
|
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
- GOTOOLCHAIN=local
|
||||||
targets:
|
targets:
|
||||||
- linux_386
|
- linux_386
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
|
- linux_arm_6
|
||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_amd64_v3
|
|
||||||
- windows_386
|
- windows_386
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- darwin_amd64_v1
|
- darwin_amd64_v1
|
||||||
@@ -43,21 +46,21 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
||||||
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
- darwin_amd64_v1
|
|
||||||
- id: android
|
- id: android
|
||||||
<<: *template
|
<<: *template
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
|
- GOTOOLCHAIN=local
|
||||||
overrides:
|
overrides:
|
||||||
- goos: android
|
- goos: android
|
||||||
goarch: arm
|
goarch: arm
|
||||||
@@ -86,22 +89,22 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
|
||||||
archives:
|
archives:
|
||||||
- &template
|
- &template
|
||||||
id: archive
|
id: archive
|
||||||
builds:
|
builds:
|
||||||
- main
|
- main
|
||||||
- android
|
- android
|
||||||
format: tar.gz
|
formats:
|
||||||
|
- tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats:
|
||||||
|
- zip
|
||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
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
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -110,10 +113,9 @@ archives:
|
|||||||
nfpms:
|
nfpms:
|
||||||
- id: package
|
- id: package
|
||||||
package_name: sing-box
|
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 }}'
|
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:
|
builds:
|
||||||
- main
|
- main
|
||||||
vendor: sagernet
|
|
||||||
homepage: https://sing-box.sagernet.org/
|
homepage: https://sing-box.sagernet.org/
|
||||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
description: The universal proxy platform.
|
description: The universal proxy platform.
|
||||||
@@ -122,15 +124,32 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
@@ -142,13 +161,34 @@ nfpms:
|
|||||||
signature:
|
signature:
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
overrides:
|
overrides:
|
||||||
deb:
|
apk:
|
||||||
conflicts:
|
contents:
|
||||||
- sing-box-beta
|
- src: release/config/config.json
|
||||||
rpm:
|
dst: /etc/sing-box/config.json
|
||||||
conflicts:
|
type: config
|
||||||
- sing-box-beta
|
|
||||||
|
|
||||||
|
- 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:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
@@ -168,4 +208,6 @@ release:
|
|||||||
ids:
|
ids:
|
||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
|
partial:
|
||||||
|
by: target
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.22-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,15 @@ 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_ech,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,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 upgrade \
|
||||||
&& apk add bash tzdata ca-certificates \
|
&& apk add bash tzdata ca-certificates nftables \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& 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"]
|
||||||
|
|||||||
168
Makefile
168
Makefile
@@ -1,37 +1,34 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0
|
||||||
TAGS_GO120 = with_quic,with_utls
|
|
||||||
TAGS_GO121 = with_ech
|
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,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)
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build_go118:
|
race:
|
||||||
go build $(PARAMS) $(MAIN)
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
|
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build_go120:
|
|
||||||
go build $(PARAMS) $(MAIN)
|
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
go build $(PARAMS) $(MAIN)
|
export GOTOOLCHAIN=local && \
|
||||||
|
go build $(PARAMS) $(MAIN) && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
generate_completions:
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -41,7 +38,7 @@ fmt:
|
|||||||
@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_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:
|
||||||
@@ -52,7 +49,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
|
||||||
@@ -63,6 +60,9 @@ proto_install:
|
|||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
|
update_certificates:
|
||||||
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
@@ -71,10 +71,9 @@ release:
|
|||||||
dist/*.deb \
|
dist/*.deb \
|
||||||
dist/*.rpm \
|
dist/*.rpm \
|
||||||
dist/*_amd64.pkg.tar.zst \
|
dist/*_amd64.pkg.tar.zst \
|
||||||
dist/*_amd64v3.pkg.tar.zst \
|
|
||||||
dist/*_arm64.pkg.tar.zst \
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
|
|
||||||
release_repo:
|
release_repo:
|
||||||
@@ -93,83 +92,134 @@ upload_android:
|
|||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
|
|
||||||
publish_android_appcenter:
|
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
|
||||||
|
|
||||||
|
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||||
|
# TODO: remove xcode clean when fix control widget fixed
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive
|
xcodebuild clean -scheme SFI && \
|
||||||
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_ios_app_store:
|
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:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFM.xcarchive && \
|
rm -rf build/SFM.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive
|
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
build_macos_independent:
|
build_macos_standalone:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.System.xcarchive && \
|
rm -rf build/SFM.System.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive
|
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
notarize_macos_independent:
|
build_macos_dmg:
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
|
||||||
|
|
||||||
wait_notarize_macos_independent:
|
|
||||||
sleep 60
|
|
||||||
|
|
||||||
export_macos_independent:
|
|
||||||
rm -rf dist/SFM
|
rm -rf dist/SFM
|
||||||
mkdir -p dist/SFM
|
mkdir -p dist/SFM
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
|
rm -rf build/SFM.System && \
|
||||||
|
rm -rf build/SFM.dmg && \
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "build/SFM.System.xcarchive" \
|
||||||
|
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
|
||||||
|
-exportPath "build/SFM.System" && \
|
||||||
|
create-dmg \
|
||||||
|
--volname "sing-box" \
|
||||||
|
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||||
|
--icon "SFM.app" 0 0 \
|
||||||
|
--hide-extension "SFM.app" \
|
||||||
|
--app-drop-link 0 0 \
|
||||||
|
--skip-jenkins \
|
||||||
|
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||||
|
|
||||||
upload_macos_independent:
|
notarize_macos_dmg:
|
||||||
|
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||||
|
--keychain-profile "notarytool-password" \
|
||||||
|
--no-s3-acceleration
|
||||||
|
|
||||||
|
upload_macos_dmg:
|
||||||
cd dist/SFM && \
|
cd dist/SFM && \
|
||||||
rm -f *.zip && \
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" *.zip
|
|
||||||
|
|
||||||
release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent
|
upload_macos_dsyms:
|
||||||
|
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||||
|
zip -r SFM.dSYMs.zip dSYMs && \
|
||||||
|
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||||
|
popd && \
|
||||||
|
cd dist/SFM && \
|
||||||
|
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
|
||||||
|
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_tvos_app_store:
|
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:
|
||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
update_macos_version:
|
||||||
|
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
|
publish_testflight:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||||
|
|
||||||
|
prepare_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||||
|
|
||||||
|
publish_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_app_store
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
cd test && \
|
cd test && \
|
||||||
@@ -185,25 +235,33 @@ test_stdio:
|
|||||||
lib_android:
|
lib_android:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
|
||||||
|
lib_android_debug:
|
||||||
|
go run ./cmd/internal/build_libbox -target android -debug
|
||||||
|
|
||||||
|
lib_apple:
|
||||||
|
go run ./cmd/internal/build_libbox -target apple
|
||||||
|
|
||||||
lib_ios:
|
lib_ios:
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
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.3
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|
||||||
publish_docs:
|
publish_docs:
|
||||||
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
docs_install:
|
docs_install:
|
||||||
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
python -m venv venv
|
||||||
|
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
@@ -211,4 +269,4 @@ clean:
|
|||||||
update:
|
update:
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ The universal proxy platform.
|
|||||||
|
|
||||||
https://sing-box.sagernet.org
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
https://community.sagernet.org/c/sing-box/
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
21
adapter/certificate.go
Normal file
21
adapter/certificate.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertificateStore interface {
|
||||||
|
LifecycleService
|
||||||
|
Pool() *x509.CertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||||
|
store := service.FromContext[CertificateStore](ctx)
|
||||||
|
if store == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return store.Pool()
|
||||||
|
}
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnectionRouter interface {
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteHandler(
|
|
||||||
metadata InboundContext,
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeHandlerWrapper{
|
|
||||||
metadata: metadata,
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteContextHandler(
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeContextHandlerWrapper{
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeHandlerWrapper struct {
|
|
||||||
metadata InboundContext
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeContextHandlerWrapper struct {
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
14
adapter/connections.go
Normal file
14
adapter/connections.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionManager interface {
|
||||||
|
Lifecycle
|
||||||
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
94
adapter/dns.go
Normal file
94
adapter/dns.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSRouter interface {
|
||||||
|
Lifecycle
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
|
||||||
|
ClearCache()
|
||||||
|
LookupReverseMapping(ip netip.Addr) (string, bool)
|
||||||
|
ResetNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSClient interface {
|
||||||
|
Start()
|
||||||
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
|
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
||||||
|
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
||||||
|
ClearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSQueryOptions struct {
|
||||||
|
Transport DNSTransport
|
||||||
|
Strategy C.DomainStrategy
|
||||||
|
LookupStrategy C.DomainStrategy
|
||||||
|
DisableCache bool
|
||||||
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
|
if options == nil {
|
||||||
|
return &DNSQueryOptions{}, nil
|
||||||
|
}
|
||||||
|
transportManager := service.FromContext[DNSTransportManager](ctx)
|
||||||
|
transport, loaded := transportManager.Transport(options.Server)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
|
}
|
||||||
|
return &DNSQueryOptions{
|
||||||
|
Transport: transport,
|
||||||
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
|
DisableCache: options.DisableCache,
|
||||||
|
RewriteTTL: options.RewriteTTL,
|
||||||
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RDRCStore interface {
|
||||||
|
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
||||||
|
SaveRDRC(transportName string, qName string, qType uint16) error
|
||||||
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransport interface {
|
||||||
|
Lifecycle
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
Dependencies() []string
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportRegistry interface {
|
||||||
|
option.DNSTransportOptionsRegistry
|
||||||
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Transports() []DNSTransport
|
||||||
|
Transport(tag string) (DNSTransport, bool)
|
||||||
|
Default() DNSTransport
|
||||||
|
FakeIP() FakeIPTransport
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||||
|
}
|
||||||
28
adapter/endpoint.go
Normal file
28
adapter/endpoint.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint interface {
|
||||||
|
Lifecycle
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
Outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointRegistry interface {
|
||||||
|
option.EndpointOptionsRegistry
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Endpoints() []Endpoint
|
||||||
|
Get(tag string) (Endpoint, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
|
||||||
|
}
|
||||||
43
adapter/endpoint/adapter.go
Normal file
43
adapter/endpoint/adapter.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
endpointType string
|
||||||
|
endpointTag string
|
||||||
|
network []string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
endpointType: endpointType,
|
||||||
|
endpointTag: endpointTag,
|
||||||
|
network: network,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(endpointType, endpointTag, network, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.endpointType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.endpointTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
147
adapter/endpoint/manager.go
Normal file
147
adapter/endpoint/manager.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.EndpointRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
endpoints []adapter.Endpoint
|
||||||
|
endpointByTag map[string]adapter.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpointByTag: make(map[string]adapter.Endpoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
if stage == adapter.StartStateStart {
|
||||||
|
// started with outbound manager
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, endpoint := range m.endpoints {
|
||||||
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
endpoints := m.endpoints
|
||||||
|
m.endpoints = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Endpoints() []adapter.Endpoint {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
endpoint, found := m.endpointByTag[tag]
|
||||||
|
return endpoint, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
endpoint, found := m.endpointByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.endpointByTag, tag)
|
||||||
|
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||||
|
return it == endpoint
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid endpoint index")
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return endpoint.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||||
|
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsEndpoint.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||||
|
return it == existsEndpoint
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid endpoint index")
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints, endpoint)
|
||||||
|
m.endpointByTag[tag] = endpoint
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/endpoint/registry.go
Normal file
72
adapter/endpoint/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.EndpointRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -4,35 +4,45 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
Service
|
LifecycleService
|
||||||
PreStarter
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
}
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
|
||||||
|
type URLTestHistory struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestHistoryStorage interface {
|
||||||
|
SetHook(hook chan<- struct{})
|
||||||
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
|
DeleteURLTestHistory(tag string)
|
||||||
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2RayServer interface {
|
||||||
|
LifecycleService
|
||||||
|
StatsService() ConnectionTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFile interface {
|
type CacheFile interface {
|
||||||
Service
|
LifecycleService
|
||||||
PreStarter
|
|
||||||
|
|
||||||
StoreFakeIP() bool
|
StoreFakeIP() bool
|
||||||
FakeIPStorage
|
FakeIPStorage
|
||||||
|
|
||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
dns.RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
@@ -40,51 +50,45 @@ type CacheFile interface {
|
|||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedRuleSet
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedRuleSet) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedRuleSet struct {
|
type SavedBinary struct {
|
||||||
Content []byte
|
Content []byte
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
LastEtag string
|
LastEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
|
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
buffer.Write(s.Content)
|
|
||||||
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
|
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = rw.WriteVString(&buffer, s.LastEtag)
|
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
var version uint8
|
var version uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &version)
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentLen, err := rw.ReadUVariant(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Content = make([]byte, contentLen)
|
|
||||||
_, err = io.ReadFull(reader, s.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -94,17 +98,13 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.LastUpdated = time.Unix(lastUpdated, 0)
|
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||||
s.LastEtag, err = rw.ReadVString(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
|
||||||
Leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Outbound
|
Outbound
|
||||||
Now() string
|
Now() string
|
||||||
@@ -122,13 +122,3 @@ func OutboundTag(detour Outbound) string {
|
|||||||
}
|
}
|
||||||
return detour.Tag()
|
return detour.Tag()
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServer interface {
|
|
||||||
Service
|
|
||||||
StatsService() V2RayStatsService
|
|
||||||
}
|
|
||||||
|
|
||||||
type V2RayStatsService interface {
|
|
||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
||||||
@@ -27,6 +26,6 @@ type FakeIPStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
type FakeIPTransport interface {
|
||||||
dns.Transport
|
DNSTransport
|
||||||
Store() FakeIPStore
|
Store() FakeIPStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,56 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionHandlerEx interface {
|
||||||
|
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use PacketHandlerEx instead
|
||||||
type PacketHandler interface {
|
type PacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use OOBPacketHandlerEx instead
|
||||||
type OOBPacketHandler interface {
|
type OOBPacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OOBPacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type PacketConnectionHandler interface {
|
type PacketConnectionHandler interface {
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketConnectionHandlerEx interface {
|
||||||
|
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use TCPConnectionHandlerEx instead
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
type UpstreamHandlerAdapter interface {
|
type UpstreamHandlerAdapter interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
N.UDPConnectionHandler
|
N.UDPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpstreamHandlerAdapterEx interface {
|
||||||
|
N.TCPConnectionHandlerEx
|
||||||
|
N.UDPConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,26 +2,42 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
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"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
Service
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InjectableInbound interface {
|
type TCPInjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
Network() []string
|
ConnectionHandlerEx
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type UDPInjectableInbound interface {
|
||||||
|
Inbound
|
||||||
|
PacketConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundRegistry interface {
|
||||||
|
option.InboundOptionsRegistry
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Inbounds() []Inbound
|
||||||
|
Get(tag string) (Inbound, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -31,27 +47,52 @@ type InboundContext struct {
|
|||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
Domain string
|
|
||||||
Protocol string
|
|
||||||
User string
|
User string
|
||||||
Outbound string
|
Outbound string
|
||||||
|
|
||||||
|
// sniffer
|
||||||
|
|
||||||
|
Protocol string
|
||||||
|
Domain string
|
||||||
|
Client string
|
||||||
|
SniffContext any
|
||||||
|
SnifferNames []string
|
||||||
|
SniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
InboundDetour string
|
// Deprecated: implement in rule action
|
||||||
LastInbound string
|
InboundDetour string
|
||||||
OriginDestination M.Socksaddr
|
LastInbound string
|
||||||
InboundOptions option.InboundOptions
|
OriginDestination M.Socksaddr
|
||||||
|
RouteOriginalDestination M.Socksaddr
|
||||||
|
// Deprecated: to be removed
|
||||||
|
//nolint:staticcheck
|
||||||
|
InboundOptions option.InboundOptions
|
||||||
|
UDPDisableDomainUnmapping bool
|
||||||
|
UDPConnect bool
|
||||||
|
UDPTimeout time.Duration
|
||||||
|
TLSFragment bool
|
||||||
|
TLSFragmentFallbackDelay time.Duration
|
||||||
|
TLSRecordFragment bool
|
||||||
|
|
||||||
|
NetworkStrategy *C.NetworkStrategy
|
||||||
|
NetworkType []C.InterfaceType
|
||||||
|
FallbackNetworkType []C.InterfaceType
|
||||||
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *ConnectionOwner
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
IPCIDRMatchSource bool
|
IPCIDRMatchSource bool
|
||||||
|
IPCIDRAcceptEmpty bool
|
||||||
|
|
||||||
SourceAddressMatch bool
|
SourceAddressMatch bool
|
||||||
SourcePortMatch bool
|
SourcePortMatch bool
|
||||||
DestinationAddressMatch bool
|
DestinationAddressMatch bool
|
||||||
@@ -62,6 +103,7 @@ type InboundContext struct {
|
|||||||
|
|
||||||
func (c *InboundContext) ResetRuleCache() {
|
func (c *InboundContext) ResetRuleCache() {
|
||||||
c.IPCIDRMatchSource = false
|
c.IPCIDRMatchSource = false
|
||||||
|
c.IPCIDRAcceptEmpty = false
|
||||||
c.SourceAddressMatch = false
|
c.SourceAddressMatch = false
|
||||||
c.SourcePortMatch = false
|
c.SourcePortMatch = false
|
||||||
c.DestinationAddressMatch = false
|
c.DestinationAddressMatch = false
|
||||||
@@ -83,15 +125,6 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
|||||||
return metadata.(*InboundContext)
|
return metadata.(*InboundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|
||||||
metadata := ContextFrom(ctx)
|
|
||||||
if metadata != nil {
|
|
||||||
return ctx, metadata
|
|
||||||
}
|
|
||||||
metadata = new(InboundContext)
|
|
||||||
return WithContext(ctx, metadata), metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
var newMetadata InboundContext
|
var newMetadata InboundContext
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
@@ -102,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
|
||||||
|
|||||||
21
adapter/inbound/adapter.go
Normal file
21
adapter/inbound/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
inboundType string
|
||||||
|
inboundTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(inboundType string, inboundTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
inboundType: inboundType,
|
||||||
|
inboundTag: inboundTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.inboundType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.inboundTag
|
||||||
|
}
|
||||||
149
adapter/inbound/manager.go
Normal file
149
adapter/inbound/manager.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.InboundManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.InboundRegistry
|
||||||
|
endpoint adapter.EndpointManager
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
inbounds []adapter.Inbound
|
||||||
|
inboundByTag map[string]adapter.Inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpoint: endpoint,
|
||||||
|
inboundByTag: make(map[string]adapter.Inbound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
inbounds := m.inbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
inbounds := m.inbounds
|
||||||
|
m.inbounds = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Inbounds() []adapter.Inbound {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.inbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
inbound, found := m.inboundByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
if found {
|
||||||
|
return inbound, true
|
||||||
|
}
|
||||||
|
return m.endpoint.Get(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
inbound, found := m.inboundByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.inboundByTag, tag)
|
||||||
|
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||||
|
return it == inbound
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return inbound.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||||
|
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsInbound.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||||
|
return it == existsInbound
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds, inbound)
|
||||||
|
m.inboundByTag[tag] = inbound
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/inbound/registry.go
Normal file
72
adapter/inbound/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.InboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
69
adapter/lifecycle.go
Normal file
69
adapter/lifecycle.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
type SimpleLifecycle interface {
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartStage uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
StartStateInitialize StartStage = iota
|
||||||
|
StartStateStart
|
||||||
|
StartStatePostStart
|
||||||
|
StartStateStarted
|
||||||
|
)
|
||||||
|
|
||||||
|
var ListStartStages = []StartStage{
|
||||||
|
StartStateInitialize,
|
||||||
|
StartStateStart,
|
||||||
|
StartStatePostStart,
|
||||||
|
StartStateStarted,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StartStage) String() string {
|
||||||
|
switch s {
|
||||||
|
case StartStateInitialize:
|
||||||
|
return "initialize"
|
||||||
|
case StartStateStart:
|
||||||
|
return "start"
|
||||||
|
case StartStatePostStart:
|
||||||
|
return "post-start"
|
||||||
|
case StartStateStarted:
|
||||||
|
return "finish-start"
|
||||||
|
default:
|
||||||
|
panic("unknown stage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lifecycle interface {
|
||||||
|
Start(stage StartStage) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LifecycleService interface {
|
||||||
|
Name() string
|
||||||
|
Lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
|
for _, service := range services {
|
||||||
|
err := service.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||||
|
for _, service := range services {
|
||||||
|
err := service.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
adapter/lifecycle_legacy.go
Normal file
52
adapter/lifecycle_legacy.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
func LegacyStart(starter any, stage StartStage) error {
|
||||||
|
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
|
||||||
|
return lifecycle.Start(stage)
|
||||||
|
}
|
||||||
|
switch stage {
|
||||||
|
case StartStateInitialize:
|
||||||
|
if preStarter, isPreStarter := starter.(interface {
|
||||||
|
PreStart() error
|
||||||
|
}); isPreStarter {
|
||||||
|
return preStarter.PreStart()
|
||||||
|
}
|
||||||
|
case StartStateStart:
|
||||||
|
if starter, isStarter := starter.(interface {
|
||||||
|
Start() error
|
||||||
|
}); isStarter {
|
||||||
|
return starter.Start()
|
||||||
|
}
|
||||||
|
case StartStateStarted:
|
||||||
|
if postStarter, isPostStarter := starter.(interface {
|
||||||
|
PostStart() error
|
||||||
|
}); isPostStarter {
|
||||||
|
return postStarter.PostStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type lifecycleServiceWrapper struct {
|
||||||
|
SimpleLifecycle
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
||||||
|
return &lifecycleServiceWrapper{
|
||||||
|
SimpleLifecycle: service,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Name() string {
|
||||||
|
return l.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
|
return LegacyStart(l.SimpleLifecycle, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
|
return l.SimpleLifecycle.Close()
|
||||||
|
}
|
||||||
60
adapter/network.go
Normal file
60
adapter/network.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Initialize(ruleSets []RuleSet)
|
||||||
|
InterfaceFinder() control.InterfaceFinder
|
||||||
|
UpdateInterfaces() error
|
||||||
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
|
NetworkInterfaces() []NetworkInterface
|
||||||
|
AutoDetectInterface() bool
|
||||||
|
AutoDetectInterfaceFunc() control.Func
|
||||||
|
ProtectFunc() control.Func
|
||||||
|
DefaultOptions() NetworkOptions
|
||||||
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
|
AutoRedirectOutputMark() uint32
|
||||||
|
AutoRedirectOutputMarkFunc() control.Func
|
||||||
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
|
PackageManager() tun.PackageManager
|
||||||
|
NeedWIFIState() bool
|
||||||
|
WIFIState() WIFIState
|
||||||
|
UpdateWIFIState()
|
||||||
|
ResetNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkOptions struct {
|
||||||
|
BindInterface string
|
||||||
|
RoutingMark uint32
|
||||||
|
DomainResolver string
|
||||||
|
DomainResolveOptions DNSQueryOptions
|
||||||
|
NetworkStrategy *C.NetworkStrategy
|
||||||
|
NetworkType []C.InterfaceType
|
||||||
|
FallbackNetworkType []C.InterfaceType
|
||||||
|
FallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type InterfaceUpdateListener interface {
|
||||||
|
InterfaceUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkInterface struct {
|
||||||
|
control.Interface
|
||||||
|
Type C.InterfaceType
|
||||||
|
DNSServers []string
|
||||||
|
Expensive bool
|
||||||
|
Constrained bool
|
||||||
|
}
|
||||||
@@ -2,8 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +19,29 @@ type Outbound interface {
|
|||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
N.Dialer
|
N.Dialer
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type 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 {
|
||||||
|
option.OutboundOptionsRegistry
|
||||||
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Outbounds() []Outbound
|
||||||
|
Outbound(tag string) (Outbound, bool)
|
||||||
|
Default() Outbound
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||||
}
|
}
|
||||||
|
|||||||
45
adapter/outbound/adapter.go
Normal file
45
adapter/outbound/adapter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
outboundType string
|
||||||
|
outboundTag string
|
||||||
|
network []string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
outboundType: outboundType,
|
||||||
|
outboundTag: outboundTag,
|
||||||
|
network: network,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.outboundType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.outboundTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
296
adapter/outbound/manager.go
Normal file
296
adapter/outbound/manager.go
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.OutboundRegistry
|
||||||
|
endpoint adapter.EndpointManager
|
||||||
|
defaultTag string
|
||||||
|
access sync.RWMutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
outbounds []adapter.Outbound
|
||||||
|
outboundByTag map[string]adapter.Outbound
|
||||||
|
dependByTag map[string][]string
|
||||||
|
defaultOutbound adapter.Outbound
|
||||||
|
defaultOutboundFallback func() (adapter.Outbound, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpoint: endpoint,
|
||||||
|
defaultTag: defaultTag,
|
||||||
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
|
dependByTag: make(map[string][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
||||||
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
if stage == adapter.StartStateStart {
|
||||||
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
|
if !loaded {
|
||||||
|
m.access.Unlock()
|
||||||
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
|
}
|
||||||
|
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 })...))
|
||||||
|
} else {
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StartTimeout)
|
||||||
|
started := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
canContinue := false
|
||||||
|
startOne:
|
||||||
|
for _, outboundToStart := range outbounds {
|
||||||
|
outboundTag := outboundToStart.Tag()
|
||||||
|
if started[outboundTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependencies := outboundToStart.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if !started[dependency] {
|
||||||
|
continue startOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
started[outboundTag] = true
|
||||||
|
canContinue = true
|
||||||
|
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||||
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
err := starter.Start(adapter.StartStateStart)
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
}
|
||||||
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
|
Start() error
|
||||||
|
}); isStarter {
|
||||||
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
err := starter.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(started) == len(outbounds) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if canContinue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return !started[it.Tag()]
|
||||||
|
})
|
||||||
|
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||||
|
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||||
|
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||||
|
return !started[it]
|
||||||
|
})
|
||||||
|
if common.Contains(oTree, problemOutboundTag) {
|
||||||
|
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
problemOutbound := m.outboundByTag[problemOutboundTag]
|
||||||
|
m.access.Unlock()
|
||||||
|
if problemOutbound == nil {
|
||||||
|
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
|
||||||
|
}
|
||||||
|
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||||
|
}
|
||||||
|
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
m.access.Lock()
|
||||||
|
if !m.started {
|
||||||
|
m.access.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.outbounds = nil
|
||||||
|
m.access.Unlock()
|
||||||
|
var err error
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||||
|
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Outbounds() []adapter.Outbound {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
return m.outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
|
m.access.RLock()
|
||||||
|
outbound, found := m.outboundByTag[tag]
|
||||||
|
m.access.RUnlock()
|
||||||
|
if found {
|
||||||
|
return outbound, true
|
||||||
|
}
|
||||||
|
return m.endpoint.Get(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
return m.defaultOutbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
outbound, found := m.outboundByTag[tag]
|
||||||
|
if !found {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.outboundByTag, tag)
|
||||||
|
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return it == outbound
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
if m.defaultOutbound == outbound {
|
||||||
|
if len(m.outbounds) > 0 {
|
||||||
|
m.defaultOutbound = m.outbounds[0]
|
||||||
|
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
||||||
|
} else {
|
||||||
|
m.defaultOutbound = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependBy := m.dependByTag[tag]
|
||||||
|
if len(dependBy) > 0 {
|
||||||
|
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
|
||||||
|
}
|
||||||
|
dependencies := outbound.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if len(m.dependByTag[dependency]) == 1 {
|
||||||
|
delete(m.dependByTag, dependency)
|
||||||
|
} else {
|
||||||
|
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
|
||||||
|
return it != tag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if started {
|
||||||
|
return common.Close(outbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
|
||||||
|
if tag == "" {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = common.Close(existsOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return it == existsOutbound
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds, outbound)
|
||||||
|
m.outboundByTag[tag] = outbound
|
||||||
|
dependencies := outbound.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
|
||||||
|
}
|
||||||
|
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
||||||
|
m.defaultOutbound = outbound
|
||||||
|
if m.started {
|
||||||
|
m.logger.Info("updated default outbound to ", outbound.Tag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/outbound/registry.go
Normal file
72
adapter/outbound/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.OutboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructors map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructors: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
|
r.constructors[outboundType] = constructor
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
@@ -1,9 +1 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
type PreStarter interface {
|
|
||||||
PreStart() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostStarter interface {
|
|
||||||
PostStart() error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,119 +2,112 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
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/common/ntp"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Lifecycle
|
||||||
PreStarter
|
|
||||||
PostStarter
|
|
||||||
|
|
||||||
Outbounds() []Outbound
|
|
||||||
Outbound(tag string) (Outbound, bool)
|
|
||||||
DefaultOutbound(network string) (Outbound, error)
|
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
|
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
GeoIPReader() *geoip.Reader
|
ConnectionRouterEx
|
||||||
LoadGeosite(code string) (Rule, error)
|
|
||||||
|
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
|
||||||
NeedWIFIState() bool
|
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
|
||||||
ClearDNSCache()
|
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
|
||||||
UpdateInterfaces() error
|
|
||||||
DefaultInterface() string
|
|
||||||
AutoDetectInterface() bool
|
|
||||||
AutoDetectInterfaceFunc() control.Func
|
|
||||||
DefaultMark() int
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
|
||||||
PackageManager() tun.PackageManager
|
|
||||||
WIFIState() WIFIState
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ClashServer() ClashServer
|
ResetNetwork()
|
||||||
SetClashServer(server ClashServer)
|
|
||||||
|
|
||||||
V2RayServer() V2RayServer
|
|
||||||
SetV2RayServer(server V2RayServer)
|
|
||||||
|
|
||||||
ResetNetwork() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
type ConnectionTracker interface {
|
||||||
return service.ContextWith(ctx, router)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
||||||
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouterFromContext(ctx context.Context) Router {
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
return service.FromContext[Router](ctx)
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadlessRule interface {
|
type ConnectionRouterEx interface {
|
||||||
Match(metadata *InboundContext) bool
|
ConnectionRouter
|
||||||
String() string
|
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
|
||||||
type Rule interface {
|
|
||||||
HeadlessRule
|
|
||||||
Service
|
|
||||||
Type() string
|
|
||||||
UpdateGeosite() error
|
|
||||||
Outbound() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule interface {
|
|
||||||
Rule
|
|
||||||
DisableCache() bool
|
|
||||||
RewriteTTL() *uint32
|
|
||||||
ClientSubnet() *netip.Prefix
|
|
||||||
WithAddressLimit() bool
|
|
||||||
MatchAddressLimit(metadata *InboundContext) bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
Name() string
|
||||||
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
|
ExtractIPSet() []*netipx.IPSet
|
||||||
|
IncRef()
|
||||||
|
DecRef()
|
||||||
|
Cleanup()
|
||||||
|
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
|
||||||
|
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
|
||||||
Close() error
|
Close() error
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
|
type HTTPStartContext struct {
|
||||||
type RuleSetStartContext interface {
|
ctx context.Context
|
||||||
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
access sync.Mutex
|
||||||
Close()
|
httpClientCache map[string]*http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
||||||
InterfaceUpdated()
|
return &HTTPStartContext{
|
||||||
|
ctx: ctx,
|
||||||
|
httpClientCache: make(map[string]*http.Client),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WIFIState struct {
|
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
||||||
SSID string
|
c.access.Lock()
|
||||||
BSSID string
|
defer c.access.Unlock()
|
||||||
|
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: C.TCPTimeout,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(c.ctx),
|
||||||
|
RootCAs: RootPoolFromContext(c.ctx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.httpClientCache[detour] = httpClient
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) Close() {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, client := range c.httpClientCache {
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
adapter/rule.go
Normal file
37
adapter/rule.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeadlessRule interface {
|
||||||
|
Match(metadata *InboundContext) bool
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
HeadlessRule
|
||||||
|
SimpleLifecycle
|
||||||
|
Type() string
|
||||||
|
Action() RuleAction
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule interface {
|
||||||
|
Rule
|
||||||
|
WithAddressLimit() bool
|
||||||
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleAction interface {
|
||||||
|
Type() string
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFinalAction(action RuleAction) bool {
|
||||||
|
switch action.Type() {
|
||||||
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
144
adapter/service/manager.go
Normal file
144
adapter/service/manager.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.ServiceRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
services []adapter.Service
|
||||||
|
serviceByTag map[string]adapter.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
serviceByTag: make(map[string]adapter.Service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
services := m.services
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, service := range services {
|
||||||
|
err := adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
services := m.services
|
||||||
|
m.services = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, service := range services {
|
||||||
|
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
err = E.Append(err, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Services() []adapter.Service {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.services
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
return service, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.serviceByTag, tag)
|
||||||
|
index := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == service
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:index], m.services[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return service.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
||||||
|
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsService.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == existsService
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.services = append(m.services, service)
|
||||||
|
m.serviceByTag[tag] = service
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,112 +4,165 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandler(
|
func NewUpstreamHandlerEx(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFunc,
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
errorHandler E.Handler,
|
) UpstreamHandlerAdapterEx {
|
||||||
) UpstreamHandlerAdapter {
|
return &myUpstreamHandlerWrapperEx{
|
||||||
return &myUpstreamHandlerWrapper{
|
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapper struct {
|
type myUpstreamHandlerWrapperEx struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFunc
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
packetHandler PacketConnectionHandlerFunc
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, myMetadata)
|
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, myMetadata)
|
w.packetHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
||||||
w.errorHandler.NewError(ctx, err)
|
|
||||||
|
type myUpstreamContextHandlerWrapperEx struct {
|
||||||
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func NewUpstreamContextHandlerEx(
|
||||||
return M.Metadata{
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
Source: metadata.Source,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
Destination: metadata.Destination,
|
) UpstreamHandlerAdapterEx {
|
||||||
}
|
return &myUpstreamContextHandlerWrapperEx{
|
||||||
}
|
|
||||||
|
|
||||||
type myUpstreamContextHandlerWrapper struct {
|
|
||||||
connectionHandler ConnectionHandlerFunc
|
|
||||||
packetHandler PacketConnectionHandlerFunc
|
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUpstreamContextHandler(
|
|
||||||
connectionHandler ConnectionHandlerFunc,
|
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
|
||||||
errorHandler E.Handler,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &myUpstreamContextHandlerWrapper{
|
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
func NewRouteHandlerEx(
|
||||||
w.errorHandler.NewError(ctx, err)
|
metadata InboundContext,
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeHandlerWrapperEx{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapperEx struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandlerEx(
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeContextHandlerWrapperEx{
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapperEx struct {
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|||||||
234
adapter/upstream_legacy.go
Normal file
234
adapter/upstream_legacy.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Deprecated
|
||||||
|
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
// Deprecated
|
||||||
|
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
func NewUpstreamHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
type myUpstreamHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: removed
|
||||||
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
|
return M.Metadata{
|
||||||
|
Source: metadata.Source.Unwrap(),
|
||||||
|
Destination: metadata.Destination.Unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +15,10 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandlerEx
|
||||||
E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
DialContext(ctx context.Context) (net.Conn, error)
|
DialContext(ctx context.Context) (net.Conn, error)
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|||||||
530
box.go
530
box.go
@@ -9,45 +9,90 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/inbound"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"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
|
||||||
router adapter.Router
|
logFactory log.Factory
|
||||||
inbounds []adapter.Inbound
|
logger log.ContextLogger
|
||||||
outbounds []adapter.Outbound
|
network *route.NetworkManager
|
||||||
logFactory log.Factory
|
endpoint *endpoint.Manager
|
||||||
logger log.ContextLogger
|
inbound *inbound.Manager
|
||||||
preServices1 map[string]adapter.Service
|
outbound *outbound.Manager
|
||||||
preServices2 map[string]adapter.Service
|
service *boxService.Manager
|
||||||
postServices map[string]adapter.Service
|
dnsTransport *dns.TransportManager
|
||||||
done chan struct{}
|
dnsRouter *dns.Router
|
||||||
|
connection *route.ConnectionManager
|
||||||
|
router *route.Router
|
||||||
|
internalService []adapter.LifecycleService
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
PlatformInterface platform.Interface
|
|
||||||
PlatformLogWriter log.PlatformWriter
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Context(
|
||||||
|
ctx context.Context,
|
||||||
|
inboundRegistry adapter.InboundRegistry,
|
||||||
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
|
) context.Context {
|
||||||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
@@ -55,6 +100,29 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
|
if endpointRegistry == nil {
|
||||||
|
return nil, E.New("missing endpoint registry in context")
|
||||||
|
}
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, E.New("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, E.New("missing outbound registry in context")
|
||||||
|
}
|
||||||
|
if dnsTransportRegistry == nil {
|
||||||
|
return nil, E.New("missing DNS transport registry in context")
|
||||||
|
}
|
||||||
|
if serviceRegistry == nil {
|
||||||
|
return nil, E.New("missing service registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
@@ -70,8 +138,9 @@ 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[adapter.PlatformInterface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -85,114 +154,246 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
router, err := route.NewRouter(
|
|
||||||
ctx,
|
var internalServices []adapter.LifecycleService
|
||||||
logFactory,
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
common.PtrValueOrDefault(options.Route),
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
common.PtrValueOrDefault(options.DNS),
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
common.PtrValueOrDefault(options.NTP),
|
len(certificateOptions.CertificatePath) > 0 ||
|
||||||
options.Inbounds,
|
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
||||||
options.PlatformInterface,
|
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
||||||
)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, E.Cause(err, "parse route options")
|
}
|
||||||
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
|
internalServices = append(internalServices, certificateStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
|
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize router")
|
||||||
|
}
|
||||||
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
|
var timeService *tls.TimeServiceWrapper
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
|
}
|
||||||
|
for i, transportOptions := range dnsOptions.Servers {
|
||||||
|
var tag string
|
||||||
|
if transportOptions.Tag != "" {
|
||||||
|
tag = transportOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = dnsTransportManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
transportOptions.Type,
|
||||||
|
transportOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize DNS server[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize dns router")
|
||||||
|
}
|
||||||
|
for i, endpointOptions := range options.Endpoints {
|
||||||
|
var tag string
|
||||||
|
if endpointOptions.Tag != "" {
|
||||||
|
tag = endpointOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
endpointCtx := ctx
|
||||||
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = endpointManager.Create(
|
||||||
|
endpointCtx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
endpointOptions.Type,
|
||||||
|
endpointOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize endpoint[", i, "]")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
|
||||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
var in adapter.Inbound
|
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
in, err = inbound.New(
|
err = inboundManager.Create(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
tag,
|
||||||
options.PlatformInterface,
|
inboundOptions.Type,
|
||||||
|
inboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
inbounds = append(inbounds, in)
|
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var out adapter.Outbound
|
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
out, err = outbound.New(
|
outboundCtx := ctx
|
||||||
ctx,
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = outboundManager.Create(
|
||||||
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions)
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
outbounds = append(outbounds, out)
|
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
for i, serviceOptions := range options.Services {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
var tag string
|
||||||
common.Must(oErr)
|
if serviceOptions.Tag != "" {
|
||||||
outbounds = append(outbounds, out)
|
tag = serviceOptions.Tag
|
||||||
return out
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = serviceManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
serviceOptions.Type,
|
||||||
|
serviceOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
|
return direct.NewOutbound(
|
||||||
|
ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger("outbound/direct"),
|
||||||
|
"direct",
|
||||||
|
option.DirectOutboundOptions{},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
if err != nil {
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
return nil, err
|
return local.NewTransport(
|
||||||
}
|
ctx,
|
||||||
if options.PlatformInterface != nil {
|
logFactory.NewLogger("dns/local"),
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
"local",
|
||||||
|
option.LocalDNSServerOptions{},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if platformInterface != nil {
|
||||||
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preServices1 := make(map[string]adapter.Service)
|
|
||||||
preServices2 := make(map[string]adapter.Service)
|
|
||||||
postServices := make(map[string]adapter.Service)
|
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
if cacheFile == nil {
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
internalServices = append(internalServices, cacheFile)
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
|
||||||
}
|
|
||||||
preServices1["cache file"] = cacheFile
|
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||||
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
|
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.SetClashServer(clashServer)
|
router.AppendTracker(clashServer)
|
||||||
preServices2["clash api"] = clashServer
|
service.MustRegister[adapter.ClashServer](ctx, 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))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create v2ray api server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
if v2rayServer.StatsService() != nil {
|
||||||
preServices2["v2ray api"] = v2rayServer
|
router.AppendTracker(v2rayServer.StatsService())
|
||||||
|
internalServices = append(internalServices, v2rayServer)
|
||||||
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create NTP service")
|
||||||
|
}
|
||||||
|
ntpService := ntp.NewService(ntp.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Dialer: ntpDialer,
|
||||||
|
Logger: logFactory.NewLogger("ntp"),
|
||||||
|
Server: ntpOptions.ServerOptions.Build(),
|
||||||
|
Interval: time.Duration(ntpOptions.Interval),
|
||||||
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
|
})
|
||||||
|
timeService.TimeService = ntpService
|
||||||
|
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
network: networkManager,
|
||||||
inbounds: inbounds,
|
endpoint: endpointManager,
|
||||||
outbounds: outbounds,
|
inbound: inboundManager,
|
||||||
createdAt: createdAt,
|
outbound: outboundManager,
|
||||||
logFactory: logFactory,
|
dnsTransport: dnsTransportManager,
|
||||||
logger: logFactory.Logger(),
|
service: serviceManager,
|
||||||
preServices1: preServices1,
|
dnsRouter: dnsRouter,
|
||||||
preServices2: preServices2,
|
connection: connectionManager,
|
||||||
postServices: postServices,
|
router: router,
|
||||||
done: make(chan struct{}),
|
createdAt: createdAt,
|
||||||
|
logFactory: logFactory,
|
||||||
|
logger: logFactory.Logger(),
|
||||||
|
internalService: internalServices,
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +404,7 @@ func (s *Box) PreStart() error {
|
|||||||
defer func() {
|
defer func() {
|
||||||
v := recover()
|
v := recover()
|
||||||
if v != nil {
|
if v != nil {
|
||||||
log.Error(E.Cause(err, "origin error"))
|
println(err.Error())
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
panic("panic on early close: " + fmt.Sprint(v))
|
panic("panic on early close: " + fmt.Sprint(v))
|
||||||
}
|
}
|
||||||
@@ -222,9 +423,9 @@ func (s *Box) Start() error {
|
|||||||
defer func() {
|
defer func() {
|
||||||
v := recover()
|
v := recover()
|
||||||
if v != nil {
|
if v != nil {
|
||||||
log.Error(E.Cause(err, "origin error"))
|
println(err.Error())
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
panic("panic on early close: " + fmt.Sprint(v))
|
println("panic on early start: " + fmt.Sprint(v))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
s.Close()
|
s.Close()
|
||||||
@@ -242,35 +443,19 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices1 {
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
|
||||||
monitor.Start("pre-start ", serviceName)
|
|
||||||
err := preService.PreStart()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for serviceName, service := range s.preServices2 {
|
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
|
||||||
monitor.Start("pre-start ", serviceName)
|
|
||||||
err := preService.PreStart()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = s.router.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start router")
|
|
||||||
}
|
|
||||||
err = s.startOutbounds()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.router.Start()
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
@@ -278,50 +463,31 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices1 {
|
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||||
err = service.Start()
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices2 {
|
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
err = service.Start()
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for i, in := range s.inbounds {
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
var tag string
|
if err != nil {
|
||||||
if in.Tag() == "" {
|
return err
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = in.Tag()
|
|
||||||
}
|
|
||||||
err = in.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return s.postStart()
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
func (s *Box) postStart() error {
|
|
||||||
for serviceName, service := range s.postServices {
|
|
||||||
err := service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, outbound := range s.outbounds {
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
if err != nil {
|
||||||
err := lateOutbound.PostStart()
|
return err
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||||
return s.router.PostStart()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Close() error {
|
func (s *Box) Close() error {
|
||||||
@@ -331,58 +497,36 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
err := common.Close(
|
||||||
var errors error
|
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
for serviceName, service := range s.postServices {
|
)
|
||||||
monitor.Start("close ", serviceName)
|
for _, lifecycleService := range s.internalService {
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
for i, in := range s.inbounds {
|
|
||||||
monitor.Start("close inbound/", in.Type(), "[", i, "]")
|
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
for i, out := range s.outbounds {
|
|
||||||
monitor.Start("close outbound/", out.Type(), "[", i, "]")
|
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
|
||||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
monitor.Start("close router")
|
|
||||||
if err := common.Close(s.router); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close router")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
monitor.Finish()
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
for serviceName, service := range s.preServices1 {
|
return E.Cause(err, "close logger")
|
||||||
monitor.Start("close ", serviceName)
|
})
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
return err
|
||||||
return E.Cause(err, "close ", serviceName)
|
}
|
||||||
})
|
|
||||||
monitor.Finish()
|
func (s *Box) Network() adapter.NetworkManager {
|
||||||
}
|
return s.network
|
||||||
for serviceName, service := range s.preServices2 {
|
|
||||||
monitor.Start("close ", serviceName)
|
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", serviceName)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close logger")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
func (s *Box) Router() adapter.Router {
|
||||||
return s.router
|
return s.router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) Inbound() adapter.InboundManager {
|
||||||
|
return s.inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
|
return s.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) LogFactory() log.Factory {
|
||||||
|
return s.logFactory
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Box) startOutbounds() error {
|
|
||||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
|
||||||
outboundTags := make(map[adapter.Outbound]string)
|
|
||||||
outbounds := make(map[string]adapter.Outbound)
|
|
||||||
for i, outboundToStart := range s.outbounds {
|
|
||||||
var outboundTag string
|
|
||||||
if outboundToStart.Tag() == "" {
|
|
||||||
outboundTag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
outboundTag = outboundToStart.Tag()
|
|
||||||
}
|
|
||||||
if _, exists := outbounds[outboundTag]; exists {
|
|
||||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
|
||||||
}
|
|
||||||
outboundTags[outboundToStart] = outboundTag
|
|
||||||
outbounds[outboundTag] = outboundToStart
|
|
||||||
}
|
|
||||||
started := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
canContinue := false
|
|
||||||
startOne:
|
|
||||||
for _, outboundToStart := range s.outbounds {
|
|
||||||
outboundTag := outboundTags[outboundToStart]
|
|
||||||
if started[outboundTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencies := outboundToStart.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if !started[dependency] {
|
|
||||||
continue startOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
started[outboundTag] = true
|
|
||||||
canContinue = true
|
|
||||||
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
|
|
||||||
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
err := starter.Start()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(started) == len(s.outbounds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if canContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return !started[outboundTags[it]]
|
|
||||||
})
|
|
||||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
|
||||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
|
||||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
|
||||||
return !started[it]
|
|
||||||
})
|
|
||||||
if common.Contains(oTree, problemOutboundTag) {
|
|
||||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
|
||||||
}
|
|
||||||
problemOutbound := outbounds[problemOutboundTag]
|
|
||||||
if problemOutbound == nil {
|
|
||||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
|
||||||
}
|
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
|
||||||
}
|
|
||||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Submodule clients/android updated: c0ce4cc97d...e08fbfcfea
Submodule clients/apple updated: 17eec08615...84d8cf1757
452
cmd/internal/app_store_connect/main.go
Normal file
452
cmd/internal/app_store_connect/main.go
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/asc-go/asc"
|
||||||
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "next_macos_project_version":
|
||||||
|
err := fetchMacOSVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_testflight":
|
||||||
|
err := publishTestflight(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "cancel_app_store":
|
||||||
|
err := cancelAppStore(ctx, os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "prepare_app_store":
|
||||||
|
err := prepareAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_app_store":
|
||||||
|
err := publishAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal("unknown action: ", os.Args[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
appID = "6673731168"
|
||||||
|
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createClient(expireDuration time.Duration) *asc.Client {
|
||||||
|
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return asc.NewClient(tokenConfig.Client())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchMacOSVersion(ctx context.Context) error {
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{"MAC_OS"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var versionID string
|
||||||
|
findVersion:
|
||||||
|
for _, version := range versions.Data {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStateReadyForSale,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
versionID = version.ID
|
||||||
|
break findVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if versionID == "" {
|
||||||
|
return E.New("no version found")
|
||||||
|
}
|
||||||
|
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse version code")
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishTestflight(ctx context.Context) error {
|
||||||
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tag := tagVersion.VersionString()
|
||||||
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
|
log.Info(tag, " list build IDs")
|
||||||
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
|
return it.ID
|
||||||
|
})
|
||||||
|
var platforms []asc.Platform
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
switch os.Args[2] {
|
||||||
|
case "ios":
|
||||||
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
|
case "macos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
|
case "tvos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
platforms = []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waitingForProcess := false
|
||||||
|
for _, platform := range platforms {
|
||||||
|
log.Info(string(platform), " list builds")
|
||||||
|
for {
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
build := builds.Data[0]
|
||||||
|
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
|
waitingForProcess = true
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
||||||
|
F.ToString("sing-box ", tagVersion.String()),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
|
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
||||||
|
log.Info("waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list submissions")
|
||||||
|
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
||||||
|
FilterBuild: []string{build.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(betaSubmissions.Data) == 0 {
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||||
|
log.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelAppStore(ctx context.Context, platform string) error {
|
||||||
|
switch platform {
|
||||||
|
case "ios":
|
||||||
|
platform = string(asc.PlatformIOS)
|
||||||
|
case "macos":
|
||||||
|
platform = string(asc.PlatformMACOS)
|
||||||
|
case "tvos":
|
||||||
|
platform = string(asc.PlatformTVOS)
|
||||||
|
}
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for {
|
||||||
|
log.Info(platform, " list versions")
|
||||||
|
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
if version.ID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " get submission")
|
||||||
|
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if response != nil && response.StatusCode == http.StatusNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
log.Info(string(platform), " ", tag, " list builds")
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(builds.Data) == 0 {
|
||||||
|
log.Fatal(platform, " ", tag, " no build found")
|
||||||
|
}
|
||||||
|
buildID := common.Ptr(builds.Data[0].ID)
|
||||||
|
if version.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " create version")
|
||||||
|
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
|
||||||
|
Platform: platform,
|
||||||
|
VersionString: tag,
|
||||||
|
}, appID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version = newVersion.Data
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Info(string(platform), " ", tag, " check build")
|
||||||
|
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if submission != nil {
|
||||||
|
log.Info(string(platform), " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " update build")
|
||||||
|
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusNoContent {
|
||||||
|
response.Write(os.Stderr)
|
||||||
|
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localization")
|
||||||
|
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
|
||||||
|
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
|
||||||
|
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
fixSubmit:
|
||||||
|
for {
|
||||||
|
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusCreated:
|
||||||
|
break fixSubmit
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
log.Fatal(string(platform), " ", tag, " not submitted")
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview:
|
||||||
|
log.Warn(string(platform), " ", tag, " waiting for review")
|
||||||
|
continue
|
||||||
|
case asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRetryable(response *asc.Response) bool {
|
||||||
|
if response == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,17 +10,23 @@ import (
|
|||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target 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.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -31,8 +37,8 @@ func main() {
|
|||||||
switch target {
|
switch target {
|
||||||
case "android":
|
case "android":
|
||||||
buildAndroid()
|
buildAndroid()
|
||||||
case "ios":
|
case "apple":
|
||||||
buildiOS()
|
buildApple()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +46,9 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
iosTags []string
|
macOSTags []string
|
||||||
|
memcTags []string
|
||||||
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,49 +59,78 @@ 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_ech", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
macOSTags = append(macOSTags, "with_dhcp")
|
||||||
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
func buildAndroid() {
|
||||||
build_shared.FindSDK()
|
build_shared.FindSDK()
|
||||||
|
|
||||||
|
var javaPath string
|
||||||
|
javaHome := os.Getenv("JAVA_HOME")
|
||||||
|
if javaHome == "" {
|
||||||
|
javaPath = "java"
|
||||||
|
} else {
|
||||||
|
javaPath = filepath.Join(javaHome, "bin", "java")
|
||||||
|
}
|
||||||
|
|
||||||
|
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(E.Cause(err, "check java version"))
|
||||||
|
}
|
||||||
|
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||||
|
log.Fatal("java version should be openjdk 17")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindTarget string
|
||||||
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
|
bindTarget = "android/arm64"
|
||||||
|
} else {
|
||||||
|
bindTarget = "android"
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
|
"-target", bindTarget,
|
||||||
"-androidapi", "21",
|
"-androidapi", "21",
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags")
|
tags := append(sharedTags, memcTags...)
|
||||||
if !debugEnabled {
|
if debugEnabled {
|
||||||
args = append(args, strings.Join(sharedTags, ","))
|
tags = append(tags, debugTags...)
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(sharedTags, 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"
|
const name = "libbox.aar"
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||||
if rw.FileExists(copyPath) {
|
if rw.IsDir(copyPath) {
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
copyPath, _ = filepath.Abs(copyPath)
|
||||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,26 +140,44 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildiOS() {
|
func buildApple() {
|
||||||
|
var bindTarget string
|
||||||
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
|
bindTarget = "ios"
|
||||||
|
} else {
|
||||||
|
bindTarget = "ios,tvos,macos"
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
|
if !withTailscale {
|
||||||
|
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
|
||||||
|
} else {
|
||||||
|
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
|
||||||
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
tags := sharedTags
|
||||||
args = append(args, "-tags")
|
if withTailscale {
|
||||||
if !debugEnabled {
|
tags = append(tags, memcTags...)
|
||||||
args = append(args, strings.Join(tags, ","))
|
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
|
||||||
}
|
}
|
||||||
|
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...)
|
||||||
@@ -134,7 +189,7 @@ func buildiOS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-apple")
|
copyPath := filepath.Join("..", "sing-box-for-apple")
|
||||||
if rw.FileExists(copyPath) {
|
if rw.IsDir(copyPath) {
|
||||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
||||||
targetDir, _ = filepath.Abs(targetDir)
|
targetDir, _ = filepath.Abs(targetDir)
|
||||||
os.RemoveAll(targetDir)
|
os.RemoveAll(targetDir)
|
||||||
|
|||||||
@@ -11,9 +11,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"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,7 +28,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
for _, path := range searchPath {
|
for _, path := range searchPath {
|
||||||
path = os.ExpandEnv(path)
|
path = os.ExpandEnv(path)
|
||||||
if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) {
|
if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) {
|
||||||
androidSDKPath = path
|
androidSDKPath = path
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -42,14 +40,6 @@ func FindSDK() {
|
|||||||
log.Fatal("android NDK not found")
|
log.Fatal("android NDK not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(E.Cause(err, "check java version"))
|
|
||||||
}
|
|
||||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
|
||||||
log.Fatal("java version should be openjdk 17")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Setenv("ANDROID_HOME", androidSDKPath)
|
os.Setenv("ANDROID_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||||
@@ -58,12 +48,16 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "26.2.11394342"
|
const fixedVersion = "28.0.13004108"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
|
||||||
|
androidNDKPath = ndkHomeEnv
|
||||||
|
return true
|
||||||
|
}
|
||||||
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -86,7 +80,7 @@ func findNDK() bool {
|
|||||||
})
|
})
|
||||||
for _, versionName := range versionNames {
|
for _, versionName := range versionNames {
|
||||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||||
if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) {
|
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
||||||
androidNDKPath = currentNDKPath
|
androidNDKPath = currentNDKPath
|
||||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||||
return true
|
return true
|
||||||
@@ -100,11 +94,11 @@ var GoBinPath string
|
|||||||
func FindMobile() {
|
func FindMobile() {
|
||||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) {
|
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
|
||||||
log.Fatal("missing gomobile installation")
|
log.Fatal("missing gomobile installation")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rw.FileExists(filepath.Join(goBin, "gobind")) {
|
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
|
||||||
log.Fatal("missing gomobile installation")
|
log.Fatal("missing gomobile installation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ func ReadTag() (string, error) {
|
|||||||
return version.String() + "-" + shortCommit, nil
|
return version.String() + "-" + shortCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadTagVersionRev() (badversion.Version, error) {
|
||||||
|
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||||
|
return badversion.Parse(currentTagRev[1:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ReadTagVersion() (badversion.Version, error) {
|
func ReadTagVersion() (badversion.Version, error) {
|
||||||
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
||||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||||
|
|||||||
@@ -1,21 +1,71 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"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 (
|
||||||
|
flagRunInCI bool
|
||||||
|
flagRunNightly bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
currentTag, err := build_shared.ReadTag()
|
flag.Parse()
|
||||||
if err != nil {
|
var (
|
||||||
log.Error(err)
|
versionStr string
|
||||||
_, err = os.Stdout.WriteString("unknown\n")
|
err error
|
||||||
|
)
|
||||||
|
if flagRunNightly {
|
||||||
|
var version badversion.Version
|
||||||
|
version, err = build_shared.ReadTagVersion()
|
||||||
|
if err == nil {
|
||||||
|
versionStr = version.String()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err = os.Stdout.WriteString(currentTag + "\n")
|
versionStr, err = build_shared.ReadTag()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if flagRunInCI {
|
||||||
log.Error(err)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = setGitHubEnv("version", versionStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
os.Stdout.WriteString("unknown\n")
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString(versionStr + "\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setGitHubEnv(name string, value string) error {
|
||||||
|
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = outputFile.WriteString(name + "=" + value + "\n")
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = outputFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString(name + "=" + value + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -12,9 +13,26 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagRunInCI bool
|
||||||
|
flagRunNightly bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
flag.Parse()
|
||||||
androidPath, err := filepath.Abs("../sing-box-for-android")
|
newVersion := common.Must1(build_shared.ReadTag())
|
||||||
|
var androidPath string
|
||||||
|
if flagRunInCI {
|
||||||
|
androidPath = "clients/android"
|
||||||
|
} else {
|
||||||
|
androidPath = "../sing-box-for-android"
|
||||||
|
}
|
||||||
|
androidPath, err := filepath.Abs(androidPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -31,22 +49,24 @@ func main() {
|
|||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
case "VERSION_NAME":
|
case "VERSION_NAME":
|
||||||
if propPair[1] != newVersion.String() {
|
if propPair[1] != newVersion {
|
||||||
|
log.Info("updated version from ", propPair[1], " to ", newVersion)
|
||||||
versionUpdated = true
|
versionUpdated = true
|
||||||
propPair[1] = newVersion.String()
|
propPair[1] = newVersion
|
||||||
log.Info("updated version to ", newVersion.String())
|
|
||||||
}
|
}
|
||||||
case "GO_VERSION":
|
case "GO_VERSION":
|
||||||
if propPair[1] != runtime.Version() {
|
if propPair[1] != runtime.Version() {
|
||||||
|
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
|
||||||
goVersionUpdated = true
|
goVersionUpdated = true
|
||||||
propPair[1] = runtime.Version()
|
propPair[1] = runtime.Version()
|
||||||
log.Info("updated Go version to ", runtime.Version())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !(versionUpdated || goVersionUpdated) {
|
if !(versionUpdated || goVersionUpdated) {
|
||||||
log.Info("version not changed")
|
log.Info("version not changed")
|
||||||
return
|
return
|
||||||
|
} else if flagRunInCI && !flagRunNightly {
|
||||||
|
log.Fatal("version changed, commit changes first.")
|
||||||
}
|
}
|
||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -13,9 +14,22 @@ import (
|
|||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var flagRunInCI bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||||
applePath, err := filepath.Abs("../sing-box-for-apple")
|
var applePath string
|
||||||
|
if flagRunInCI {
|
||||||
|
applePath = "clients/apple"
|
||||||
|
} else {
|
||||||
|
applePath = "../sing-box-for-apple"
|
||||||
|
}
|
||||||
|
applePath, err := filepath.Abs(applePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -26,8 +40,8 @@ func main() {
|
|||||||
common.Must(decoder.Decode(&project))
|
common.Must(decoder.Decode(&project))
|
||||||
objectsMap := project["objects"].(map[string]any)
|
objectsMap := project["objects"].(map[string]any)
|
||||||
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
||||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
|
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
|
||||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
|
||||||
if updated0 || updated1 {
|
if updated0 || updated1 {
|
||||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||||
}
|
}
|
||||||
|
|||||||
71
cmd/internal/update_certificates/main.go
Normal file
71
cmd/internal/update_certificates/main.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := updateMozillaIncludedRootCAs()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMozillaIncludedRootCAs() error {
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
geoIndex := slices.Index(header, "Geographic Focus")
|
||||||
|
nameIndex := slices.Index(header, "Common Name or Certificate Name")
|
||||||
|
certIndex := slices.Index(header, "PEM Info")
|
||||||
|
|
||||||
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var mozillaIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mozillaIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record[geoIndex] == "China" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
generated.WriteString("\n // ")
|
||||||
|
generated.WriteString(record[nameIndex])
|
||||||
|
generated.WriteString("\n")
|
||||||
|
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
cert := record[certIndex]
|
||||||
|
// Remove single quotes
|
||||||
|
cert = cert[1 : len(cert)-1]
|
||||||
|
generated.WriteString(cert)
|
||||||
|
generated.WriteString("`))\n")
|
||||||
|
}
|
||||||
|
generated.WriteString("}\n")
|
||||||
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
|
}
|
||||||
71
cmd/sing-box/cmd.go
Normal file
71
cmd/sing-box/cmd.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalCtx context.Context
|
||||||
|
configPaths []string
|
||||||
|
configDirectories []string
|
||||||
|
workingDir string
|
||||||
|
disableColor bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainCommand = &cobra.Command{
|
||||||
|
Use: "sing-box",
|
||||||
|
PersistentPreRun: preRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||||
|
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||||
|
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||||
|
}
|
||||||
|
|
||||||
|
func preRun(cmd *cobra.Command, args []string) {
|
||||||
|
globalCtx = context.Background()
|
||||||
|
sudoUser := os.Getenv("SUDO_USER")
|
||||||
|
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||||
|
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||||
|
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
||||||
|
sudoUserObject, _ := user.Lookup(sudoUser)
|
||||||
|
if sudoUserObject != nil {
|
||||||
|
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
||||||
|
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sudoUID > 0 && sudoGID > 0 {
|
||||||
|
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
||||||
|
}
|
||||||
|
if disableColor {
|
||||||
|
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
||||||
|
}
|
||||||
|
if workingDir != "" {
|
||||||
|
_, err := os.Stat(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
||||||
|
}
|
||||||
|
err = os.Chdir(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
|
configPaths = append(configPaths, "config.json")
|
||||||
|
}
|
||||||
|
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ func check() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(globalCtx)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pqSignatureSchemesEnabled bool
|
|
||||||
|
|
||||||
var commandGenerateECHKeyPair = &cobra.Command{
|
var commandGenerateECHKeyPair = &cobra.Command{
|
||||||
Use: "ech-keypair <plain_server_name>",
|
Use: "ech-keypair <plain_server_name>",
|
||||||
Short: "Generate TLS ECH key pair",
|
Short: "Generate TLS ECH key pair",
|
||||||
@@ -24,12 +22,11 @@ var commandGenerateECHKeyPair = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes")
|
|
||||||
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateECHKeyPair(serverName string) error {
|
func generateECHKeyPair(serverName string) error {
|
||||||
configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled)
|
configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSKeyPair(serverName string) error {
|
func generateTLSKeyPair(serverName string) error {
|
||||||
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func geoipExport(countryCode string) error {
|
|||||||
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
|
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
|
||||||
}
|
}
|
||||||
var plainRuleSet option.PlainRuleSetCompat
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
plainRuleSet.Version = C.RuleSetVersion1
|
plainRuleSet.Version = C.RuleSetVersion2
|
||||||
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func geositeExport(category string) error {
|
|||||||
headlessRule.DomainKeyword = defaultRule.DomainKeyword
|
headlessRule.DomainKeyword = defaultRule.DomainKeyword
|
||||||
headlessRule.DomainRegex = defaultRule.DomainRegex
|
headlessRule.DomainRegex = defaultRule.DomainRegex
|
||||||
var plainRuleSet option.PlainRuleSetCompat
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
plainRuleSet.Version = C.RuleSetVersion1
|
plainRuleSet.Version = C.RuleSetVersion2
|
||||||
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var commandMerge = &cobra.Command{
|
var commandMerge = &cobra.Command{
|
||||||
Use: "merge <output>",
|
Use: "merge <output-path>",
|
||||||
Short: "Merge configurations",
|
Short: "Merge configurations",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := merge(args[0])
|
err := merge(args[0])
|
||||||
@@ -54,7 +54,11 @@ func merge(outputPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = rw.WriteFile(outputPath, buffer.Bytes())
|
err = rw.MkdirParent(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,29 +68,19 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for index, inbound := range options.Inbounds {
|
for _, inbound := range options.Inbounds {
|
||||||
rawOptions, err := inbound.RawOptions()
|
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
|
||||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Inbounds[index] = inbound
|
|
||||||
}
|
}
|
||||||
for index, outbound := range options.Outbounds {
|
for _, outbound := range options.Outbounds {
|
||||||
rawOptions, err := outbound.RawOptions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch outbound.Type {
|
switch outbound.Type {
|
||||||
case C.TypeSSH:
|
case C.TypeSSH:
|
||||||
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Outbounds[index] = outbound
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -134,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
||||||
if options.PrivateKeyPath != "" {
|
if options.PrivateKeyPath != "" {
|
||||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimStringArray(array []string) []string {
|
func trimStringArray(array []string) []string {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var commandRuleSet = &cobra.Command{
|
var commandRuleSet = &cobra.Command{
|
||||||
Use: "rule-set",
|
Use: "rule-set",
|
||||||
Short: "Manage rule sets",
|
Short: "Manage rule-sets",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -55,10 +57,6 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ruleSet := plainRuleSet.Upgrade()
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".json") {
|
if strings.HasSuffix(sourcePath, ".json") {
|
||||||
@@ -73,7 +71,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, ruleSet)
|
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)
|
||||||
@@ -82,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
|
||||||
|
}
|
||||||
|
|||||||
89
cmd/sing-box/cmd_rule_set_convert.go
Normal file
89
cmd/sing-box/cmd_rule_set_convert.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/convertor/adguard"
|
||||||
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagRuleSetConvertType string
|
||||||
|
flagRuleSetConvertOutput string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetConvert = &cobra.Command{
|
||||||
|
Use: "convert [source-path]",
|
||||||
|
Short: "Convert adguard DNS filter to rule-set",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := convertRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetConvert)
|
||||||
|
commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertType, "type", "t", "", "Source type, available: adguard")
|
||||||
|
commandRuleSetConvert.Flags().StringVarP(&flagRuleSetConvertOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRuleSet(sourcePath string) error {
|
||||||
|
var (
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sourcePath == "stdin" {
|
||||||
|
reader = os.Stdin
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var rules []option.HeadlessRule
|
||||||
|
switch flagRuleSetConvertType {
|
||||||
|
case "adguard":
|
||||||
|
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
||||||
|
case "":
|
||||||
|
return E.New("source type is required")
|
||||||
|
default:
|
||||||
|
return E.New("unsupported source type: ", flagRuleSetConvertType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var outputPath string
|
||||||
|
if flagRuleSetConvertOutput == flagRuleSetCompileDefaultOutput {
|
||||||
|
if strings.HasSuffix(sourcePath, ".txt") {
|
||||||
|
outputPath = sourcePath[:len(sourcePath)-4] + ".srs"
|
||||||
|
} else {
|
||||||
|
outputPath = sourcePath + ".srs"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outputPath = flagRuleSetConvertOutput
|
||||||
|
}
|
||||||
|
outputFile, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
101
cmd/sing-box/cmd_rule_set_decompile.go
Normal file
101
cmd/sing-box/cmd_rule_set_decompile.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagRuleSetDecompileOutput string
|
||||||
|
|
||||||
|
const flagRuleSetDecompileDefaultOutput = "<file_name>.json"
|
||||||
|
|
||||||
|
var commandRuleSetDecompile = &cobra.Command{
|
||||||
|
Use: "decompile [binary-path]",
|
||||||
|
Short: "Decompile rule-set binary to json",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := decompileRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetDecompile)
|
||||||
|
commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decompileRuleSet(sourcePath string) error {
|
||||||
|
var (
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sourcePath == "stdin" {
|
||||||
|
reader = os.Stdin
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruleSet, err := srs.Read(reader, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
|
outputPath = sourcePath[:len(sourcePath)-4] + ".json"
|
||||||
|
} else {
|
||||||
|
outputPath = sourcePath + ".json"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outputPath = flagRuleSetDecompileOutput
|
||||||
|
}
|
||||||
|
outputFile, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(outputFile)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(ruleSet)
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -2,17 +2,21 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"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"
|
||||||
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"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
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/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -20,8 +24,8 @@ import (
|
|||||||
var flagRuleSetMatchFormat string
|
var flagRuleSetMatchFormat string
|
||||||
|
|
||||||
var commandRuleSetMatch = &cobra.Command{
|
var commandRuleSetMatch = &cobra.Command{
|
||||||
Use: "match <rule-set path> <domain>",
|
Use: "match <rule-set path> <IP address/domain>",
|
||||||
Short: "Check if a domain matches the rule set",
|
Short: "Check if an IP address or a domain matches the rule-set",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := ruleSetMatch(args[0], args[1])
|
err := ruleSetMatch(args[0], args[1])
|
||||||
@@ -53,33 +57,48 @@ 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")
|
||||||
}
|
}
|
||||||
var plainRuleSet option.PlainRuleSet
|
if flagRuleSetMatchFormat == "" {
|
||||||
|
switch filepath.Ext(sourcePath) {
|
||||||
|
case ".json":
|
||||||
|
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
||||||
|
case ".srs":
|
||||||
|
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
var compat option.PlainRuleSetCompat
|
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
plainRuleSet = compat.Upgrade()
|
|
||||||
case C.RuleSetFormatBinary:
|
case C.RuleSetFormatBinary:
|
||||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return E.New("unknown rule set format: ", flagRuleSetMatchFormat)
|
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||||
|
}
|
||||||
|
plainRuleSet, err := ruleSet.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipAddress := M.ParseAddr(domain)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
if ipAddress.IsValid() {
|
||||||
|
metadata.Destination = M.SocksaddrFrom(ipAddress, 0)
|
||||||
|
} else {
|
||||||
|
metadata.Domain = domain
|
||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
if currentRule.Match(&adapter.InboundContext{
|
if currentRule.Match(&metadata) {
|
||||||
Domain: domain,
|
println(F.ToString("match rules.[", i, "]: ", currentRule))
|
||||||
}) {
|
|
||||||
println("match rules.[", i, "]: "+currentRule.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ruleSetPaths []string
|
||||||
|
ruleSetDirectories []string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetMerge = &cobra.Command{
|
||||||
|
Use: "merge <output-path>",
|
||||||
|
Short: "Merge rule-set source files",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := mergeRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetMerge)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleSetEntry struct {
|
||||||
|
content []byte
|
||||||
|
path string
|
||||||
|
options option.PlainRuleSetCompat
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAt(path string) (*RuleSetEntry, error) {
|
||||||
|
var (
|
||||||
|
configContent []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if path == "stdin" {
|
||||||
|
configContent, err = io.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
configContent, err = os.ReadFile(path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
|
}
|
||||||
|
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
|
}
|
||||||
|
return &RuleSetEntry{
|
||||||
|
content: configContent,
|
||||||
|
path: path,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSet() ([]*RuleSetEntry, error) {
|
||||||
|
var optionsList []*RuleSetEntry
|
||||||
|
for _, path := range ruleSetPaths {
|
||||||
|
optionsEntry, err := readRuleSetAt(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
for _, directory := range ruleSetDirectories {
|
||||||
|
entries, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read rule-set directory at ", directory)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(optionsList, func(i, j int) bool {
|
||||||
|
return optionsList[i].path < optionsList[j].path
|
||||||
|
})
|
||||||
|
return optionsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
|
||||||
|
optionsList, err := readRuleSet()
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, err
|
||||||
|
}
|
||||||
|
if len(optionsList) == 1 {
|
||||||
|
return optionsList[0].options, nil
|
||||||
|
}
|
||||||
|
var optionVersion uint8
|
||||||
|
for _, options := range optionsList {
|
||||||
|
if optionVersion < options.options.Version {
|
||||||
|
optionVersion = options.options.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mergedMessage json.RawMessage
|
||||||
|
for _, options := range optionsList {
|
||||||
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
|
||||||
|
}
|
||||||
|
mergedOptions.Version = optionVersion
|
||||||
|
return mergedOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRuleSet(outputPath string) error {
|
||||||
|
mergedOptions, err := readRuleSetAndMerge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
if existsContent, err := os.ReadFile(outputPath); err != nil {
|
||||||
|
if string(existsContent) == buffer.String() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rw.MkdirParent(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputPath, _ = filepath.Abs(outputPath)
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
95
cmd/sing-box/cmd_rule_set_upgrade.go
Normal file
95
cmd/sing-box/cmd_rule_set_upgrade.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetUpgradeFlagWrite bool
|
||||||
|
|
||||||
|
var commandRuleSetUpgrade = &cobra.Command{
|
||||||
|
Use: "upgrade <source-path>",
|
||||||
|
Short: "Upgrade rule-set json",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := upgradeRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSetUpgrade.Flags().BoolVarP(&commandRuleSetUpgradeFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgradeRuleSet(sourcePath string) error {
|
||||||
|
var (
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sourcePath == "stdin" {
|
||||||
|
reader = os.Stdin
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plainRuleSetCompat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch plainRuleSetCompat.Version {
|
||||||
|
case C.RuleSetVersion1:
|
||||||
|
default:
|
||||||
|
log.Info("already up-to-date")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(plainRuleSetCompat)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
outputPath, _ := filepath.Abs(sourcePath)
|
||||||
|
if !commandRuleSetUpgradeFlagWrite || sourcePath == "stdin" {
|
||||||
|
os.Stdout.WriteString(buffer.String() + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bytes.Equal(content, buffer.Bytes()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
output, err := os.Create(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output")
|
||||||
|
}
|
||||||
|
_, err = output.Write(buffer.Bytes())
|
||||||
|
output.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write output")
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
options, err := json.UnmarshalExtended[option.Options](configContent)
|
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
|||||||
}
|
}
|
||||||
var mergedMessage json.RawMessage
|
var mergedMessage json.RawMessage
|
||||||
for _, options := range optionsList {
|
for _, options := range optionsList {
|
||||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage)
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mergedOptions option.Options
|
var mergedOptions option.Options
|
||||||
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||||
}
|
}
|
||||||
@@ -188,9 +188,12 @@ func run() error {
|
|||||||
cancel()
|
cancel()
|
||||||
closeCtx, closed := context.WithCancel(context.Background())
|
closeCtx, closed := context.WithCancel(context.Background())
|
||||||
go closeMonitor(closeCtx)
|
go closeMonitor(closeCtx)
|
||||||
instance.Close()
|
err = instance.Close()
|
||||||
closed()
|
closed()
|
||||||
if osSignal != syscall.SIGHUP {
|
if osSignal != syscall.SIGHUP {
|
||||||
|
if err != nil {
|
||||||
|
log.Error(E.Cause(err, "sing-box did not closed properly"))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -23,9 +26,11 @@ func init() {
|
|||||||
func createPreStartedClient() (*box.Box, error) {
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{Options: options})
|
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
}
|
}
|
||||||
@@ -36,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
||||||
if outboundTag == "" {
|
if outboundTag == "" {
|
||||||
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
return instance.Outbound().Default(), nil
|
||||||
} else {
|
} else {
|
||||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
return nil, E.New("outbound not found: ", outboundTag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func connect(address string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -32,7 +34,10 @@ func init() {
|
|||||||
commandTools.AddCommand(commandFetch)
|
commandTools.AddCommand(commandFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient *http.Client
|
var (
|
||||||
|
httpClient *http.Client
|
||||||
|
http3Client *http.Client
|
||||||
|
)
|
||||||
|
|
||||||
func fetch(args []string) error {
|
func fetch(args []string) error {
|
||||||
instance, err := createPreStartedClient()
|
instance, err := createPreStartedClient()
|
||||||
@@ -43,7 +48,7 @@ func fetch(args []string) error {
|
|||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -53,8 +58,16 @@ func fetch(args []string) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer httpClient.CloseIdleConnections()
|
defer httpClient.CloseIdleConnections()
|
||||||
|
if C.WithQUIC {
|
||||||
|
err = initializeHTTP3Client(instance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer http3Client.CloseIdleConnections()
|
||||||
|
}
|
||||||
for _, urlString := range args {
|
for _, urlString := range args {
|
||||||
parsedURL, err := url.Parse(urlString)
|
var parsedURL *url.URL
|
||||||
|
parsedURL, err = url.Parse(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -63,16 +76,27 @@ func fetch(args []string) error {
|
|||||||
parsedURL.Scheme = "http"
|
parsedURL.Scheme = "http"
|
||||||
fallthrough
|
fallthrough
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
err = fetchHTTP(parsedURL)
|
err = fetchHTTP(httpClient, parsedURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchHTTP(parsedURL *url.URL) error {
|
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
||||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
|
}
|
||||||
@@ -4,12 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
|
||||||
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"
|
||||||
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"
|
||||||
@@ -45,7 +43,7 @@ func syncTime() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,7 @@ func syncTime() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if commandSyncTimeWrite {
|
if commandSyncTimeWrite {
|
||||||
err = settings.SetSystemTime(response.Time)
|
err = ntp.SetSystemTime(response.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write time to system")
|
return E.Cause(err, "write time to system")
|
||||||
}
|
}
|
||||||
|
|||||||
28
cmd/sing-box/generate_completions.go
Normal file
28
cmd/sing-box/generate_completions.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//go:build generate && generate_completions
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := generateCompletions()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCompletions() error {
|
||||||
|
err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,74 +1,11 @@
|
|||||||
|
//go:build !generate
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/sagernet/sing-box/log"
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
globalCtx context.Context
|
|
||||||
configPaths []string
|
|
||||||
configDirectories []string
|
|
||||||
workingDir string
|
|
||||||
disableColor bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var mainCommand = &cobra.Command{
|
|
||||||
Use: "sing-box",
|
|
||||||
PersistentPreRun: preRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
|
||||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
|
||||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := mainCommand.Execute(); err != nil {
|
if err := mainCommand.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func preRun(cmd *cobra.Command, args []string) {
|
|
||||||
globalCtx = context.Background()
|
|
||||||
sudoUser := os.Getenv("SUDO_USER")
|
|
||||||
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
|
||||||
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
|
||||||
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
|
||||||
sudoUserObject, _ := user.Lookup(sudoUser)
|
|
||||||
if sudoUserObject != nil {
|
|
||||||
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
|
||||||
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sudoUID > 0 && sudoGID > 0 {
|
|
||||||
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
|
||||||
}
|
|
||||||
if disableColor {
|
|
||||||
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
|
||||||
}
|
|
||||||
if workingDir != "" {
|
|
||||||
_, err := os.Stat(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
|
||||||
}
|
|
||||||
err = os.Chdir(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
|
||||||
configPaths = append(configPaths, "config.json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
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,31 +0,0 @@
|
|||||||
//go:build go1.21 && !without_badtls && with_ech
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
_ "unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/cloudflare-tls"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
|
||||||
tlsConn, loaded := common.Cast[*tls.Conn](conn)
|
|
||||||
if !loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, func() error {
|
|
||||||
return echReadRecord(tlsConn)
|
|
||||||
}, func() error {
|
|
||||||
return echHandlePostHandshakeMessage(tlsConn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
|
|
||||||
func echReadRecord(c *tls.Conn) error
|
|
||||||
|
|
||||||
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func echHandlePostHandshakeMessage(c *tls.Conn) 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")))
|
||||||
}
|
}
|
||||||
|
|||||||
4394
common/certificate/mozilla.go
Normal file
4394
common/certificate/mozilla.go
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user