mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-01-13 06:07:14 +08:00
Compare commits
681 Commits
v1.8.16
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ca03fa94e | ||
|
|
bdfd1d27b5 | ||
|
|
e9774fc237 | ||
|
|
07a0dafa41 | ||
|
|
0ca13452b8 | ||
|
|
36425d2a6e | ||
|
|
39ba1f7952 | ||
|
|
394e117998 | ||
|
|
446df149bd | ||
|
|
d9025857fe | ||
|
|
ced3e75bf3 | ||
|
|
961c352127 | ||
|
|
c715154309 | ||
|
|
b38a41249f | ||
|
|
7265b5ac3f | ||
|
|
e7c72c011f | ||
|
|
a54e1f2be4 | ||
|
|
5d94a62a83 | ||
|
|
ad468e462d | ||
|
|
6738ecf68e | ||
|
|
36968909a1 | ||
|
|
7f6ceb39f7 | ||
|
|
fa64775f07 | ||
|
|
a6792dda69 | ||
|
|
3572209cbd | ||
|
|
dd757ca27c | ||
|
|
04b433dd97 | ||
|
|
6bf0376773 | ||
|
|
74df63add2 | ||
|
|
c40326dfd7 | ||
|
|
a3ba3eefb6 | ||
|
|
9cf22114a1 | ||
|
|
a903e80356 | ||
|
|
a610a4c89a | ||
|
|
b451f8929d | ||
|
|
81f8f398c7 | ||
|
|
aea123842b | ||
|
|
bd7503d506 | ||
|
|
903214a0f0 | ||
|
|
e403abe360 | ||
|
|
c123f163c2 | ||
|
|
93312d29e5 | ||
|
|
36cb0f00bd | ||
|
|
cadcb47074 | ||
|
|
c6afcd5fb6 | ||
|
|
ed5f7e7af5 | ||
|
|
9d3401b6f0 | ||
|
|
d60ef656cc | ||
|
|
a83253f3d7 | ||
|
|
2969a189e6 | ||
|
|
d41840132a | ||
|
|
f14fd1cbee | ||
|
|
cd51f57535 | ||
|
|
4956e65824 | ||
|
|
2185a730d2 | ||
|
|
fcfb0a302a | ||
|
|
b40bf56e4e | ||
|
|
27ad487545 | ||
|
|
e914183996 | ||
|
|
acfda31e59 | ||
|
|
f9dd3aef72 | ||
|
|
b24ef88a80 | ||
|
|
b16a5f03fe | ||
|
|
2f1fabb318 | ||
|
|
1ec2966433 | ||
|
|
8a4b0a9eb0 | ||
|
|
4e8ee302a6 | ||
|
|
18a4104737 | ||
|
|
1a32d18c16 | ||
|
|
412bc17c12 | ||
|
|
9491b67f3c | ||
|
|
cb4f943f50 | ||
|
|
b69a376aa1 | ||
|
|
12f4a014e0 | ||
|
|
9cc7907234 | ||
|
|
21a9658519 | ||
|
|
7f436f5318 | ||
|
|
dcfde8dc92 | ||
|
|
898db92d51 | ||
|
|
8dd0e388a2 | ||
|
|
40f0a541bf | ||
|
|
1762d6c8cc | ||
|
|
195248801d | ||
|
|
4a825c0260 | ||
|
|
514c9e5a22 | ||
|
|
2f366aed2e | ||
|
|
c0c88f3d73 | ||
|
|
d0344bcff8 | ||
|
|
a6ebb3061c | ||
|
|
fe57507fd9 | ||
|
|
83c5370eec | ||
|
|
1a48453bea | ||
|
|
3167e5cec0 | ||
|
|
5148c5786f | ||
|
|
3edfb0e335 | ||
|
|
d3248a4f8e | ||
|
|
30e10be95d | ||
|
|
cced1477a0 | ||
|
|
9f5dcb1591 | ||
|
|
ce5c51d3ba | ||
|
|
11f670c8a6 | ||
|
|
a387ae9590 | ||
|
|
4ae497106d | ||
|
|
1f4fc2e7bb | ||
|
|
ae44b86b0d | ||
|
|
8276a443bc | ||
|
|
1e2f251bb3 | ||
|
|
845010b535 | ||
|
|
a0c63ba1cf | ||
|
|
2b82366148 | ||
|
|
ab1fa13ebe | ||
|
|
4740ba2425 | ||
|
|
4b0ee28f1c | ||
|
|
6ec0291d4e | ||
|
|
118131fcaf | ||
|
|
197b319f9a | ||
|
|
8b579bf3ec | ||
|
|
cbade89ab1 | ||
|
|
d20397c15d | ||
|
|
19f8907296 | ||
|
|
e943de5300 | ||
|
|
4064f8dd80 | ||
|
|
2acd206821 | ||
|
|
4c6fd94d97 | ||
|
|
fd54b10d97 | ||
|
|
6830089d3c | ||
|
|
6768a22f67 | ||
|
|
e8b02cd664 | ||
|
|
fbb0ecfb83 | ||
|
|
a31842feaa | ||
|
|
79325ead2e | ||
|
|
81b7cd718a | ||
|
|
ea1a3ae8f1 | ||
|
|
593ededd3e | ||
|
|
82ea7a3cc5 | ||
|
|
56a45ad578 | ||
|
|
4976085ddb | ||
|
|
fcdd4df446 | ||
|
|
12b077f33b | ||
|
|
702d2c06ca | ||
|
|
7951a5c4bf | ||
|
|
c2141f09e7 | ||
|
|
ef640ed309 | ||
|
|
5fa5f3fbb9 | ||
|
|
2ee372e758 | ||
|
|
11f0513bce | ||
|
|
b65da77267 | ||
|
|
33272a0499 | ||
|
|
87b643a388 | ||
|
|
6d1695a686 | ||
|
|
3a54924045 | ||
|
|
573300bc22 | ||
|
|
7f300dbf0c | ||
|
|
5464862ee6 | ||
|
|
337b4b814e | ||
|
|
105b306d07 | ||
|
|
de23e51077 | ||
|
|
40ce850bd9 | ||
|
|
2485f4831f | ||
|
|
aac0d6a6a5 | ||
|
|
f557bf7da4 | ||
|
|
6fc0a40c2a | ||
|
|
f3cdcad541 | ||
|
|
5a8e9c25a4 | ||
|
|
836b6487e4 | ||
|
|
b1107b9810 | ||
|
|
0cceea75da | ||
|
|
4b21c9aed3 | ||
|
|
cde6e33ec9 | ||
|
|
5dce7e4e25 | ||
|
|
9359844149 | ||
|
|
8222f43eea | ||
|
|
04e6439b51 | ||
|
|
bd86732f68 | ||
|
|
d4f11e6d68 | ||
|
|
00f3147242 | ||
|
|
7cbf5b004c | ||
|
|
87fff12fd9 | ||
|
|
a02723e63f | ||
|
|
146b14ab55 | ||
|
|
b2829219a0 | ||
|
|
116cd70a3a | ||
|
|
c569f478af | ||
|
|
b6b51c51c8 | ||
|
|
fb7a9d8d61 | ||
|
|
3fe02a658a | ||
|
|
5f93ff6c3a | ||
|
|
10376f5b4d | ||
|
|
1ea00fad81 | ||
|
|
cfcf2a63d1 | ||
|
|
66025f2889 | ||
|
|
c9cd26d6d3 | ||
|
|
caee152adf | ||
|
|
eb433d9462 | ||
|
|
87d8b97d9a | ||
|
|
9d15ecf1f9 | ||
|
|
4f45c5faa5 | ||
|
|
26de58933f | ||
|
|
31b508d372 | ||
|
|
955a569181 | ||
|
|
d141d01d0c | ||
|
|
4e826abebf | ||
|
|
4433641e30 | ||
|
|
a196a16c55 | ||
|
|
8c0bf15901 | ||
|
|
dbd9125686 | ||
|
|
923b5d7229 | ||
|
|
f90fae22aa | ||
|
|
b065595f58 | ||
|
|
308f8a7459 | ||
|
|
050f596e8f | ||
|
|
3b47d0846e | ||
|
|
7f23a1cb65 | ||
|
|
446315cf1f | ||
|
|
eed05549fc | ||
|
|
2b4a8d235b | ||
|
|
83686ebfaa | ||
|
|
79c6f99384 | ||
|
|
ca8ef209a7 | ||
|
|
cbcab89c7e | ||
|
|
abd551e9f7 | ||
|
|
10dbeb4335 | ||
|
|
6afd721ced | ||
|
|
5c0bc361d3 | ||
|
|
3a2ac9d0bf | ||
|
|
1785178762 | ||
|
|
1976d02ec9 | ||
|
|
3ba733079e | ||
|
|
6a8a85f83a | ||
|
|
409e4e8f12 | ||
|
|
486d005986 | ||
|
|
cb1afb33e6 | ||
|
|
38ed2cc387 | ||
|
|
b043db8260 | ||
|
|
27742da2c6 | ||
|
|
fbae89d017 | ||
|
|
58c28b4aeb | ||
|
|
ca1c4b63f6 | ||
|
|
18ab291e0c | ||
|
|
e011b746dc | ||
|
|
f4b23c6565 | ||
|
|
7d36cad3e0 | ||
|
|
a576a4b183 | ||
|
|
f38d3f786a | ||
|
|
402067d281 | ||
|
|
97fdcb4228 | ||
|
|
bfbccc2721 | ||
|
|
f67c70c4a2 | ||
|
|
83e5fa4f3c | ||
|
|
4e1a6f0fd1 | ||
|
|
ab0b9a6220 | ||
|
|
b80e319655 | ||
|
|
6232e230d9 | ||
|
|
028e1114e6 | ||
|
|
af7a76da67 | ||
|
|
d44c78b819 | ||
|
|
d0c80fc80d | ||
|
|
1f49bfc6a5 | ||
|
|
dd52732e1f | ||
|
|
4af53ab364 | ||
|
|
462bc3cfba | ||
|
|
84c8e24a6c | ||
|
|
9fe4ee75b7 | ||
|
|
f1116cee60 | ||
|
|
800b8b50cc | ||
|
|
ef1c165cc5 | ||
|
|
bb0e561caf | ||
|
|
09d84c42e9 | ||
|
|
7ddc4a2525 | ||
|
|
882975ce5a | ||
|
|
30bf7be429 | ||
|
|
c53c1bfc51 | ||
|
|
72170d8b6b | ||
|
|
e9b3c53a0d | ||
|
|
7afed1e74d | ||
|
|
6ed636840b | ||
|
|
f0dfbc2e66 | ||
|
|
0d0fe7ef7a | ||
|
|
59aa5e1b88 | ||
|
|
3e52f73e3c | ||
|
|
d4ca42715a | ||
|
|
c847c21f3b | ||
|
|
87ab8e5128 | ||
|
|
54c6513fd4 | ||
|
|
5e6a5ae01d | ||
|
|
19ba9cbe91 | ||
|
|
16641fc4b5 | ||
|
|
aa4134f4a6 | ||
|
|
1c4e246788 | ||
|
|
d9ebb9b2dc | ||
|
|
2eed70e17d | ||
|
|
58c48664e2 | ||
|
|
a608c5a1db | ||
|
|
0dd74cf072 | ||
|
|
922ae98a4a | ||
|
|
800b33c626 | ||
|
|
0563c9750e | ||
|
|
907a182f64 | ||
|
|
0995fa41fe | ||
|
|
2916b1b977 | ||
|
|
8212325980 | ||
|
|
5f3ae64f0c | ||
|
|
7a2f42f8d5 | ||
|
|
53552d73cc | ||
|
|
19e884bebf | ||
|
|
78a1e37e89 | ||
|
|
2d3126b752 | ||
|
|
0dbab7bcd7 | ||
|
|
ab15822ee3 | ||
|
|
8b2fe32a33 | ||
|
|
dd2a40e64d | ||
|
|
33a4336b1d | ||
|
|
ab5d7cf3d2 | ||
|
|
2abeda9c42 | ||
|
|
63eb0539b3 | ||
|
|
8284a0ef8f | ||
|
|
17207fc5e4 | ||
|
|
52a2c63682 | ||
|
|
1685c61e44 | ||
|
|
681e943848 | ||
|
|
8530bdb0de | ||
|
|
29e37e8a82 | ||
|
|
e129b1e90d | ||
|
|
5922caff89 | ||
|
|
4afe2d0cff | ||
|
|
2d3210e4b8 | ||
|
|
673a9ae063 | ||
|
|
607c2a6d31 | ||
|
|
b585b26f29 | ||
|
|
1067171e6a | ||
|
|
86a225cda1 | ||
|
|
6f8e253dec | ||
|
|
6a211a0bb9 | ||
|
|
335845a9b2 | ||
|
|
b70d0abebe | ||
|
|
3d21128159 | ||
|
|
eca99771ca | ||
|
|
2cba2c4d59 | ||
|
|
306fa51475 | ||
|
|
6d6f1c6967 | ||
|
|
152959824f | ||
|
|
a977b6357e | ||
|
|
16eee1b89c | ||
|
|
dde0a4f272 | ||
|
|
e15dff94b5 | ||
|
|
e466b0497c | ||
|
|
b9cb93d3c2 | ||
|
|
8d46f7e14c | ||
|
|
4b616f5cd0 | ||
|
|
06b4a7ce4d | ||
|
|
4c12e1686b | ||
|
|
225d151cd3 | ||
|
|
d451078e72 | ||
|
|
ce2384cccc | ||
|
|
be43f66b63 | ||
|
|
71a6d89c23 | ||
|
|
89792aee9d | ||
|
|
b786a50aee | ||
|
|
b38a53e629 | ||
|
|
52381a3c03 | ||
|
|
4b01eb4398 | ||
|
|
c5de08bea6 | ||
|
|
8cb63db6c0 | ||
|
|
eef74b2c7d | ||
|
|
a1714cc4ce | ||
|
|
958b13ebb5 | ||
|
|
22c50a70c6 | ||
|
|
94c7970fd6 | ||
|
|
a71762b5da | ||
|
|
5033cbceea | ||
|
|
dcd7e92c45 | ||
|
|
2d7ca4a6a6 | ||
|
|
925a985cc0 | ||
|
|
613c63b165 | ||
|
|
d4c7cd02fd | ||
|
|
db5f18b98c | ||
|
|
c81d8e488a | ||
|
|
1d9e6bc2f3 | ||
|
|
ae327eb7e6 | ||
|
|
e893fa1828 | ||
|
|
1982c2366e | ||
|
|
117de1fd3c | ||
|
|
07c35ed52a | ||
|
|
e17c068821 | ||
|
|
88d40d6367 | ||
|
|
527caa3711 | ||
|
|
c6a31f457c | ||
|
|
9b7841178a | ||
|
|
480c7d7db7 | ||
|
|
c2f6c89987 | ||
|
|
0a8470cb14 | ||
|
|
efdc70fbf7 | ||
|
|
f35fb08aeb | ||
|
|
1bb0beaa43 | ||
|
|
03131c72db | ||
|
|
7b59379d73 | ||
|
|
a7a83624c5 | ||
|
|
3a7a78ff3a | ||
|
|
5679d717ee | ||
|
|
740a6b0dcd | ||
|
|
2522cfd7be | ||
|
|
a0822cb440 | ||
|
|
ca9a902213 | ||
|
|
f4fd8b8fad | ||
|
|
14a6636a41 | ||
|
|
30cb22afb1 | ||
|
|
66dd7808b6 | ||
|
|
f1ff454e67 | ||
|
|
4576f56ec8 | ||
|
|
9b1855f719 | ||
|
|
3e590a4eb1 | ||
|
|
ef4a3c1cae | ||
|
|
5635254ebc | ||
|
|
ce6c0dc690 | ||
|
|
aeb12d9e3b | ||
|
|
de53a3b94e | ||
|
|
2f52aa7ed8 | ||
|
|
ca50c9cbe6 | ||
|
|
33186ca5e6 | ||
|
|
e80ca67fee | ||
|
|
dd4ba823f5 | ||
|
|
0658c9545b | ||
|
|
480eac7235 | ||
|
|
8a6a5385ff | ||
|
|
5178dc500a | ||
|
|
1a1c49de36 | ||
|
|
c8b17ad18d | ||
|
|
4be32e99b2 | ||
|
|
5af90684c4 | ||
|
|
369d8944cf | ||
|
|
4ce65fc74c | ||
|
|
93f72db9fd | ||
|
|
ff4331a7a8 | ||
|
|
a8559a1b46 | ||
|
|
42aea01fb5 | ||
|
|
a7909f8671 | ||
|
|
b287d6419b | ||
|
|
d54d20abea | ||
|
|
868799ef04 | ||
|
|
db934f0832 | ||
|
|
53b04d560b | ||
|
|
1410b6335b | ||
|
|
cab2fdefd3 | ||
|
|
ff8b66aacb | ||
|
|
15318976f6 | ||
|
|
a168f5360e | ||
|
|
9dbdf92c27 | ||
|
|
96fb680d45 | ||
|
|
5836afc41f | ||
|
|
7d0a80b501 | ||
|
|
73e0d4a666 | ||
|
|
7463561856 | ||
|
|
743435d6e6 | ||
|
|
8cd9a74376 | ||
|
|
6be3c35db8 | ||
|
|
0e2304c403 | ||
|
|
a2b773135a | ||
|
|
9cb6816383 | ||
|
|
46d8d9ef02 | ||
|
|
34141c940e | ||
|
|
3e7002d24c | ||
|
|
ae62a0fb52 | ||
|
|
98a72b6fb4 | ||
|
|
4f6f12616c | ||
|
|
c87cf8ff52 | ||
|
|
f7bd98b13c | ||
|
|
d8934cf839 | ||
|
|
ce8c415d43 | ||
|
|
034a485afe | ||
|
|
384d07999c | ||
|
|
513f18bf53 | ||
|
|
817fa72874 | ||
|
|
0a252ac15d | ||
|
|
6ba0dbafd7 | ||
|
|
59e5d24280 | ||
|
|
7d3d6b05e3 | ||
|
|
55e045d098 | ||
|
|
5a96ef632d | ||
|
|
1f570d9cef | ||
|
|
2d7b0e8cd4 | ||
|
|
ec1fd008c4 | ||
|
|
17825b25f2 | ||
|
|
83ae38497b | ||
|
|
7b4a686b74 | ||
|
|
48ac662298 | ||
|
|
1a238cbb7d | ||
|
|
44b1dd0e67 | ||
|
|
0df2446f82 | ||
|
|
85b3c2328f | ||
|
|
571777483b | ||
|
|
1ffb8a92cd | ||
|
|
480748403a | ||
|
|
bd0841a75b | ||
|
|
83bab5dd90 | ||
|
|
bc4bf3d38f | ||
|
|
94c02f090e | ||
|
|
5af750b336 | ||
|
|
6cb58d9315 | ||
|
|
8cd3f5448d | ||
|
|
b98f29bf3e | ||
|
|
6877ca5201 | ||
|
|
71cfea8aae | ||
|
|
afc7ec5506 | ||
|
|
057e6284b2 | ||
|
|
ccc4b7b2cf | ||
|
|
9fbb6fbb3b | ||
|
|
2c72864935 | ||
|
|
e3276df725 | ||
|
|
85a1c33709 | ||
|
|
b7aacd3245 | ||
|
|
4ec5c78c34 | ||
|
|
b30e05d1bc | ||
|
|
e733148c0b | ||
|
|
9f8bb47633 | ||
|
|
ceb6eac8e7 | ||
|
|
cf182b0482 | ||
|
|
94338c94e9 | ||
|
|
e4939dc1db | ||
|
|
8809cbda81 | ||
|
|
9bdf72d658 | ||
|
|
b0272c172a | ||
|
|
25c7bc0c82 | ||
|
|
47fad1fbfd | ||
|
|
897521defd | ||
|
|
86257531ee | ||
|
|
82bd5f3046 | ||
|
|
19f3f709b2 | ||
|
|
6a70ae6408 | ||
|
|
8c180b9cfd | ||
|
|
5164a82185 | ||
|
|
af7232d545 | ||
|
|
b3ab49f323 | ||
|
|
e2b9f71384 | ||
|
|
c30f5d4796 | ||
|
|
75729ce779 | ||
|
|
c7534c4e36 | ||
|
|
e45cef542e | ||
|
|
3b06af882d | ||
|
|
697156f6f6 | ||
|
|
d8485bd5af | ||
|
|
3632e83faa | ||
|
|
7086d286be | ||
|
|
d497de916f | ||
|
|
acbf36e21b | ||
|
|
93cff1a576 | ||
|
|
7677ac980d | ||
|
|
bc28cad8f8 | ||
|
|
57a41f3b4b | ||
|
|
f406b2dee0 | ||
|
|
a35d5719f4 | ||
|
|
363e86c585 | ||
|
|
b1c6471eeb | ||
|
|
a931507dd6 | ||
|
|
67c2a29065 | ||
|
|
5f0642a671 | ||
|
|
3fed0c773f | ||
|
|
d7c5a0fc5f | ||
|
|
781aaee21f | ||
|
|
7970f240de | ||
|
|
88ae774cce | ||
|
|
c259e4e4a6 | ||
|
|
3a8c5f38e8 | ||
|
|
12c3908e8c | ||
|
|
1919a1e86f | ||
|
|
1b607ff874 | ||
|
|
7496413e5c | ||
|
|
297a9ae7bd | ||
|
|
a247997e38 | ||
|
|
6b1bf312d7 | ||
|
|
c90affe7db | ||
|
|
3d0feae462 | ||
|
|
f04cb0b288 | ||
|
|
9438517130 | ||
|
|
3d9137cb6f | ||
|
|
6838a0ae55 | ||
|
|
3971532798 | ||
|
|
d36e027a50 | ||
|
|
f357245f93 | ||
|
|
c0a98f74fc | ||
|
|
9a953c070f | ||
|
|
ab3c00e96b | ||
|
|
f1c439c2aa | ||
|
|
6baad79f98 | ||
|
|
98e85b7f3c | ||
|
|
002d08bf83 | ||
|
|
8674ed5a0d | ||
|
|
815a959c96 | ||
|
|
1cfed96aba | ||
|
|
592c2daf58 | ||
|
|
5d7dfbbcac | ||
|
|
3dd3bf94d4 | ||
|
|
c0c23fdfeb | ||
|
|
34e0a2fb41 | ||
|
|
83eef6bc1f | ||
|
|
2be03c56cb | ||
|
|
790f908f0b | ||
|
|
0df7fa23f8 | ||
|
|
41d03d1856 | ||
|
|
160316d53c | ||
|
|
1562e1ffb9 | ||
|
|
11b04807b9 | ||
|
|
b612da26eb | ||
|
|
030c9efc8c | ||
|
|
08d1840344 | ||
|
|
f0547bc04d | ||
|
|
498d8eb3cc | ||
|
|
0c7303960a | ||
|
|
513182adf3 | ||
|
|
a3b306aaa4 | ||
|
|
f650d87083 | ||
|
|
85e2ebc6f7 | ||
|
|
9e93c19161 | ||
|
|
96e8b8b279 | ||
|
|
1a95add8e3 | ||
|
|
ac628a9427 | ||
|
|
4a1c0d7124 | ||
|
|
661cb901de | ||
|
|
c6d9d430bf | ||
|
|
1dfec5056d | ||
|
|
2a5eb3db7d | ||
|
|
4c9e4b9de7 | ||
|
|
644901d1a5 | ||
|
|
7e24239c9a | ||
|
|
4b7947cca9 | ||
|
|
4c82ef8a1b | ||
|
|
30af792777 | ||
|
|
33daa0c94b | ||
|
|
70383c50cc | ||
|
|
521d8ef6a1 | ||
|
|
4531a7e228 | ||
|
|
a342db3e28 | ||
|
|
60553a6c26 | ||
|
|
59f6685774 | ||
|
|
4cb2a128db | ||
|
|
8a4217fdf5 | ||
|
|
7cf5ee8afd | ||
|
|
2becdd6414 | ||
|
|
edae38c620 | ||
|
|
36f427f22b | ||
|
|
c27d652d80 | ||
|
|
0f65aa8ed8 | ||
|
|
22535d8643 | ||
|
|
529f206d33 | ||
|
|
964859b4bc | ||
|
|
8deb953aec | ||
|
|
a0040f13dd | ||
|
|
d8994b7603 | ||
|
|
b277bacdf6 | ||
|
|
9288a7c0dc | ||
|
|
c40fc44a34 | ||
|
|
02cd3b8c74 | ||
|
|
a7e198e1e2 | ||
|
|
9e6d7a3cb0 | ||
|
|
a4bc422ed1 | ||
|
|
59819e2a1b | ||
|
|
573fb4f643 | ||
|
|
558cfcc507 | ||
|
|
39675b7ef7 | ||
|
|
16de0937a8 | ||
|
|
c69d38ae82 | ||
|
|
73a001dd7a | ||
|
|
c8f6ba9ff0 | ||
|
|
308f0c64c3 | ||
|
|
ce637c0c23 | ||
|
|
0d130a0489 | ||
|
|
01a3b4912b | ||
|
|
b8c0768b16 | ||
|
|
4c51636788 | ||
|
|
1113ee7fa2 | ||
|
|
e13f9f59da | ||
|
|
60b2c349d2 | ||
|
|
c6a57b2cc1 | ||
|
|
079d0bd8a9 | ||
|
|
8320732743 | ||
|
|
c0f3bf66fd | ||
|
|
e4f9d03bef | ||
|
|
7acd5a623b | ||
|
|
ee2000f6e1 | ||
|
|
74d233dd64 | ||
|
|
eb4f9429e6 | ||
|
|
028a640b1b |
45
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
45
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -7,20 +7,22 @@ body:
|
||||
description: |-
|
||||
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
|
||||
options:
|
||||
- label: I have read all the comments in the issue template and ensured that this issue meet the requirements.
|
||||
required: true
|
||||
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
|
||||
required: true
|
||||
- label: I provided the complete config and logs, rather than just providing the truncated parts based on my own judgment.
|
||||
required: true
|
||||
- label: I searched issues and did not find any similar issues.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Version of Xray-core
|
||||
validations:
|
||||
required: true
|
||||
- label: The problem can be successfully reproduced in the latest Release
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a detailed description of the error. And the information you think valuable.
|
||||
description: |-
|
||||
Please provide a detailed description of the error. And the information you think valuable.
|
||||
If the problem occurs after the update, please provide the **specific** version
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -38,50 +40,39 @@ body:
|
||||
### For config
|
||||
Please provide the configuration files that can reproduce the problem, including the server and client.
|
||||
Don't just paste a big exported config file here. Eliminate useless inbound/outbound, rules, options, this can help determine the problem, if you really want to get help.
|
||||
After removing parts that do not affect reproduction, provide the actual running **complete** file.
|
||||
meaning of complete: This config can be directly used to start the core, **not a truncated part of the config**. For fields like keys, use newly generated valid parameters that have not been actually used to fill in.
|
||||
|
||||
### For logs
|
||||
Please set the log level to debug first.
|
||||
Please set the log level to debug and dnsLog to true first.
|
||||
Restart Xray-core, then operate according to the reproduction method, try to reduce the irrelevant part in the log.
|
||||
Remember to delete parts with personal information (such as UUID and IP).
|
||||
Provide the log of Xray-core, not the log output by the panel or other things.
|
||||
|
||||
### Finally
|
||||
After removing parts that do not affect reproduction, provide the actual running **complete** file, do not only provide inbound or outbound or a few lines of logs based on your own judgment.
|
||||
Put the content between the preset ```<details><pre><code>``` ```</code></pre></details>``` in the text box.
|
||||
If the problem is very clear that only related to one end (such as core startup failure/crash after correctly writing the config according to the documents), N/A can be filled in for unnecessary areas below.
|
||||
The specific content to be filled in each of the following text boxes needs to be placed between ```<details><pre><code>``` and ```</code></pre></details>```, like this
|
||||
```
|
||||
<details><pre><code>
|
||||
(config)
|
||||
</code></pre></details>
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Client config
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Server config
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Client log
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Server log
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
47
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
47
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -7,20 +7,22 @@ body:
|
||||
description: |-
|
||||
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
|
||||
options:
|
||||
- label: 我读完了 issue 模板中的所有注释,确保填写符合要求。
|
||||
required: true
|
||||
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
|
||||
required: true
|
||||
- label: 我搜索了issues,没有发现已提出的类似问题。
|
||||
- label: 我提供了完整的配置文件和日志,而不是出于自己的判断只给出截取的部分。
|
||||
required: true
|
||||
- label: 我搜索了 issues, 没有发现已提出的类似问题。
|
||||
required: true
|
||||
- label: 问题在 Release 最新的版本上可以成功复现
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 使用的Xray-core版本
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 描述
|
||||
description: 请提供错误的详细描述。以及你认为有价值的信息。
|
||||
description: |-
|
||||
请提供错误的详细描述。以及你认为有价值的信息。
|
||||
如果问题在更新后出现,请提供**具体**出现问题的版本号。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -38,50 +40,39 @@ body:
|
||||
### 对于配置文件
|
||||
请提供可以重现问题的配置文件,包括服务端和客户端。
|
||||
不要直接在这里黏贴一大段导出的 config 文件。去掉无用的出入站、规则、选项,这可以帮助确定问题,如果你真的想得到帮助。
|
||||
在去掉不影响复现的部分后,提供实际运行的**完整**文件。
|
||||
完整的含义:可以直接使用这个配置启动核心,**不是截取的部分配置**。对于密钥等参数使用重新生成未实际使用的有效参数填充。
|
||||
|
||||
### 对于日志
|
||||
请先将日志等级设置为 debug.
|
||||
请先将日志等级设置为 debug, dnsLog 设置为true.
|
||||
重启 Xray-core ,再按复现方式操作,尽量减少日志中的无关部分。
|
||||
记得删除有关个人信息(如UUID与IP)的部分。
|
||||
提供 Xray-core 的日志,而不是面板或者别的东西输出的日志。
|
||||
|
||||
### 最后
|
||||
在去掉不影响复现的部分后,提供实际运行的**完整**文件,不要出于自己的判断只提供入站出站或者几行日志。
|
||||
把内容放在文本框预置的 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间。
|
||||
如果问题十分明确只出现在某一端(如按文档正确编写配置后核心启动失败/崩溃),可以在下面不需要的项目填入N/A.
|
||||
把下面的每格具体内容需要放在 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间,如
|
||||
```
|
||||
<details><pre><code>
|
||||
(config)
|
||||
</code></pre></details>
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 客户端配置
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 服务端配置
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 客户端日志
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 服务端日志
|
||||
value: |-
|
||||
<details><pre><code>
|
||||
|
||||
</code></pre></details>
|
||||
validations:
|
||||
required: true
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Community Support and Questions
|
||||
url: https://github.com/XTLS/Xray-core/discussions
|
||||
about: Please ask and answer questions there. The issue tracker is for issues with core.
|
||||
76
.github/docker/Dockerfile
vendored
76
.github/docker/Dockerfile
vendored
@@ -1,22 +1,62 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM --platform=$BUILDPLATFORM golang:latest AS build
|
||||
|
||||
# Build xray-core
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
ARG TARGETOS TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||
|
||||
FROM --platform=${TARGETPLATFORM} alpine:latest
|
||||
WORKDIR /root
|
||||
COPY .github/docker/files/config.json /etc/xray/config.json
|
||||
COPY --from=build /src/xray /usr/bin/xray
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache tzdata ca-certificates \
|
||||
&& mkdir -p /var/log/xray /usr/share/xray \
|
||||
&& chmod +x /usr/bin/xray \
|
||||
&& wget -O /usr/share/xray/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \
|
||||
&& wget -O /usr/share/xray/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
# Download geodat into a staging directory
|
||||
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||
|
||||
VOLUME /etc/xray
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENTRYPOINT [ "/usr/bin/xray" ]
|
||||
CMD [ "-config", "/etc/xray/config.json" ]
|
||||
RUN mkdir -p /tmp/empty
|
||||
|
||||
# Create config files with empty JSON content
|
||||
RUN mkdir -p /tmp/usr/local/etc/xray
|
||||
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||
{
|
||||
"log": {
|
||||
"error": "/var/log/xray/error.log",
|
||||
"loglevel": "warning",
|
||||
"access": "none",
|
||||
"dnsLog": false
|
||||
}
|
||||
}
|
||||
EOF
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
/tmp/var/log/xray/access.log \
|
||||
/tmp/var/log/xray/error.log
|
||||
|
||||
# Build finally image
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
|
||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||
|
||||
VOLUME /usr/local/etc/xray
|
||||
VOLUME /var/log/xray
|
||||
|
||||
ARG TZ=Etc/UTC
|
||||
ENV TZ=$TZ
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/xray" ]
|
||||
CMD [ "-confdir", "/usr/local/etc/xray/" ]
|
||||
|
||||
71
.github/docker/Dockerfile.usa
vendored
Normal file
71
.github/docker/Dockerfile.usa
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM --platform=$BUILDPLATFORM golang:latest AS build
|
||||
|
||||
# Build xray-core
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||
|
||||
# Download geodat into a staging directory
|
||||
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||
|
||||
RUN mkdir -p /tmp/empty
|
||||
|
||||
# Create config files with empty JSON content
|
||||
RUN mkdir -p /tmp/usr/local/etc/xray
|
||||
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||
{
|
||||
"log": {
|
||||
"error": "/var/log/xray/error.log",
|
||||
"loglevel": "warning",
|
||||
"access": "none",
|
||||
"dnsLog": false
|
||||
}
|
||||
}
|
||||
EOF
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
/tmp/var/log/xray/access.log \
|
||||
/tmp/var/log/xray/error.log
|
||||
|
||||
# Build finally image
|
||||
# Note on Distroless Base Image and Architecture Support:
|
||||
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
|
||||
# - linux/amd64
|
||||
# - linux/arm/v7
|
||||
# - linux/arm64/v8
|
||||
# - linux/ppc64le
|
||||
# - linux/s390x
|
||||
# - Upon inspection, the blob contents of the Distroless images across these architectures are nearly identical, with only minor differences in metadata (e.g., 'Architecture' field in the manifest).
|
||||
# - Due to this similarity in content, it is feasible to forcibly specify a single platform (e.g., '--platform=linux/amd64') for unsupported architectures, as the core image content remains compatible with statically compiled binaries like Go applications.
|
||||
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
|
||||
|
||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||
|
||||
VOLUME /usr/local/etc/xray
|
||||
VOLUME /var/log/xray
|
||||
|
||||
ARG TZ=Etc/UTC
|
||||
ENV TZ=$TZ
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/xray" ]
|
||||
CMD [ "-confdir", "/usr/local/etc/xray/" ]
|
||||
18
.github/docker/files/config.json
vendored
18
.github/docker/files/config.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"inbounds": [{
|
||||
"port": 9000,
|
||||
"protocol": "vmess",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "1eb6e917-774b-4a84-aff6-b058577c60a5",
|
||||
"level": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}],
|
||||
"outbounds": [{
|
||||
"protocol": "freedom",
|
||||
"settings": {}
|
||||
}]
|
||||
}
|
||||
142
.github/workflows/docker.yml
vendored
142
.github/workflows/docker.yml
vendored
@@ -1,45 +1,133 @@
|
||||
name: Build docker image
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
- released
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Docker image tag:"
|
||||
required: true
|
||||
latest:
|
||||
description: "Set to latest"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
build-and-push:
|
||||
if: (github.event.action != 'published') || (github.event.action == 'published' && github.event.release.prerelease == true)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/xray-core
|
||||
flavor: latest=true
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
- name: Set repository and image name to lowercase
|
||||
env:
|
||||
IMAGE_NAME: "${{ github.repository }}"
|
||||
run: |
|
||||
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
|
||||
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
|
||||
|
||||
- name: Validate and extract tag
|
||||
run: |
|
||||
SOURCE_TAG="${{ github.event.inputs.tag }}"
|
||||
if [[ -z "$SOURCE_TAG" ]]; then
|
||||
SOURCE_TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [[ -z "$SOURCE_TAG" ]]; then
|
||||
SOURCE_TAG="${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
|
||||
if [[ -z "$SOURCE_TAG" ]]; then
|
||||
echo "Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$SOURCE_TAG" =~ ^v[0-9]+\.[0-9] ]]; then
|
||||
IMAGE_TAG="${SOURCE_TAG#v}"
|
||||
else
|
||||
IMAGE_TAG="$SOURCE_TAG"
|
||||
fi
|
||||
|
||||
echo "Docker image tag: '$IMAGE_TAG'."
|
||||
echo "IMAGE_TAG=$IMAGE_TAG" >>${GITHUB_ENV}
|
||||
|
||||
LATEST=false
|
||||
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]] || [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.latest }}" == "true" ]]; then
|
||||
LATEST=true
|
||||
fi
|
||||
|
||||
echo "Latest: '$LATEST'."
|
||||
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Add support for more platforms with QEMU (optional)
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push
|
||||
|
||||
- name: Build Docker image (main architectures)
|
||||
id: build_main_arches
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: .github/docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm/v7
|
||||
linux/arm64/v8
|
||||
linux/ppc64le
|
||||
linux/s390x
|
||||
provenance: false
|
||||
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Build Docker image (additional architectures)
|
||||
id: build_additional_arches
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: .github/docker/Dockerfile.usa
|
||||
platforms: |
|
||||
linux/386
|
||||
linux/arm/v6
|
||||
linux/riscv64
|
||||
linux/loong64
|
||||
provenance: false
|
||||
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Create manifest list and push
|
||||
run: |
|
||||
echo "Creating multi-arch manifest with tag: '${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}'."
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
|
||||
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_main_arches.outputs.digest }} \
|
||||
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_additional_arches.outputs.digest }}
|
||||
|
||||
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||
echo "Adding 'latest' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:latest'."
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ env.FULL_IMAGE_NAME }}:latest \
|
||||
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
fi
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
|
||||
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
||||
fi
|
||||
|
||||
180
.github/workflows/release-win7.yml
vendored
Normal file
180
.github/workflows/release-win7.yml
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
name: Build and Release for Windows 7
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
|
||||
- name: Check Assets Existence
|
||||
id: check-assets
|
||||
run: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('geoip.dat' 'geosite.dat')
|
||||
for FILE_NAME in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking ${FILE_NAME}..."
|
||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||
echo -e "${FILE_NAME} exists."
|
||||
else
|
||||
echo -e "${FILE_NAME} does not exist."
|
||||
echo "missing=true" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
done
|
||||
LIST=('amd64' 'x86')
|
||||
for ARCHITECTURE in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists."
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
|
||||
echo "missing=true" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Sleep for 90 seconds if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
run: sleep 90
|
||||
|
||||
build:
|
||||
needs: check-assets
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# BEGIN Windows 7
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
assetname: win7-64
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
assetname: win7-32
|
||||
# END Windows 7
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOOS: ${{ matrix.goos}}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
_NAME=${{ matrix.assetname }}
|
||||
echo "GOOS: ${{ matrix.goos }}, GOARCH: ${{ matrix.goarch }}, RELEASE_NAME: $_NAME"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Setup patched builder
|
||||
run: |
|
||||
GOSDK=$(go env GOROOT)
|
||||
rm -r $GOSDK/*
|
||||
cd $GOSDK
|
||||
curl -O -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
|
||||
unzip ./go-for-win7-linux-amd64.zip -d $GOSDK
|
||||
rm ./go-for-win7-linux-amd64.zip
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Build Xray
|
||||
run: |
|
||||
mkdir -p build_assets
|
||||
COMMID=$(git describe --always --dirty)
|
||||
echo 'Building Xray for Windows 7...'
|
||||
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
|
||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
|
||||
- name: Add additional assets into package
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == '386' ]]; then
|
||||
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
run: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
|
||||
- name: Create ZIP archive
|
||||
if: github.event_name == 'release'
|
||||
shell: bash
|
||||
run: |
|
||||
pushd build_assets || exit 1
|
||||
touch -mt $(date +%Y01010000) *
|
||||
zip -9vr ../Xray-${{ env.ASSET_NAME }}.zip .
|
||||
popd || exit 1
|
||||
FILE=./Xray-${{ env.ASSET_NAME }}.zip
|
||||
DGST=$FILE.dgst
|
||||
for METHOD in {"md5","sha1","sha256","sha512"}
|
||||
do
|
||||
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
|
||||
done
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./Xray-${{ env.ASSET_NAME }}.zip*
|
||||
tag: ${{ github.ref }}
|
||||
file_glob: true
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Xray-${{ env.ASSET_NAME }}
|
||||
path: |
|
||||
./build_assets/*
|
||||
210
.github/workflows/release.yml
vendored
210
.github/workflows/release.yml
vendored
@@ -1,76 +1,79 @@
|
||||
name: Build and Release
|
||||
|
||||
# NOTE: This Github Actions file depends on the Makefile.
|
||||
# Building the correct package requires the correct binaries generated by the Makefile. To
|
||||
# ensure the correct output, the Makefile must accept the appropriate input and compile the
|
||||
# correct file with the correct name. If you need to modify this file, please ensure it won't
|
||||
# disrupt the Makefile.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/release.yml"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/release.yml"
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v4
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Update Geodat
|
||||
id: update
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
max_attempts: 60
|
||||
command: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||
FILE_NAME="${INFO[2]}.dat"
|
||||
echo -e "Verifying HASH key..."
|
||||
HASH="$(curl -sL "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
||||
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
|
||||
continue
|
||||
else
|
||||
echo -e "Downloading https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat..."
|
||||
curl -L "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat" -o ./resources/${FILE_NAME}
|
||||
echo -e "Verifying HASH key..."
|
||||
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Save Cache
|
||||
uses: actions/cache/save@v4
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-${{ github.sha }}-${{ github.run_number }}
|
||||
key: xray-wintun-
|
||||
|
||||
- name: Check Assets Existence
|
||||
id: check-assets
|
||||
run: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('geoip.dat' 'geosite.dat')
|
||||
for FILE_NAME in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking ${FILE_NAME}..."
|
||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||
echo -e "${FILE_NAME} exists."
|
||||
else
|
||||
echo -e "${FILE_NAME} does not exist."
|
||||
echo "missing=true" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
done
|
||||
LIST=('amd64' 'x86' 'arm64' 'arm')
|
||||
for ARCHITECTURE in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists."
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
|
||||
echo "missing=true" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Trigger Asset Update Workflow if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: 'scheduled-assets-update.yml',
|
||||
ref: context.ref
|
||||
});
|
||||
console.log('Triggered scheduled-assets-update.yml due to missing assets on branch:', context.ref);
|
||||
|
||||
- name: Sleep for 90 seconds if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
run: sleep 90
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
needs: check-assets
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
@@ -78,12 +81,13 @@ jobs:
|
||||
# Include amd64 on all platforms.
|
||||
goos: [windows, freebsd, openbsd, linux, darwin]
|
||||
goarch: [amd64, 386]
|
||||
patch-assetname: [""]
|
||||
exclude:
|
||||
# Exclude i386 on darwin
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
include:
|
||||
# BEIGIN MacOS ARM64
|
||||
# BEGIN MacOS ARM64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
# END MacOS ARM64
|
||||
@@ -102,6 +106,11 @@ jobs:
|
||||
- goos: android
|
||||
goarch: arm64
|
||||
# END Android ARM 8
|
||||
# BEGIN Android AMD64
|
||||
- goos: android
|
||||
goarch: amd64
|
||||
patch-assetname: android-amd64
|
||||
# END Android AMD64
|
||||
# Windows ARM
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
@@ -162,38 +171,93 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Show workflow information
|
||||
- name: Set up NDK
|
||||
if: matrix.goos == 'android'
|
||||
run: |
|
||||
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||
wget -qO android-ndk.zip https://dl.google.com/android/repository/android-ndk-r28b-linux.zip
|
||||
unzip android-ndk.zip
|
||||
rm android-ndk.zip
|
||||
declare -A arches=(
|
||||
["amd64"]="x86_64-linux-android24-clang"
|
||||
["arm64"]="aarch64-linux-android24-clang"
|
||||
)
|
||||
echo CC="$(realpath android-ndk-*/toolchains/llvm/prebuilt/linux-x86_64/bin)/${arches[${{ matrix.goarch }}]}" >> $GITHUB_ENV
|
||||
echo CGO_ENABLED=1 >> $GITHUB_ENV
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
_NAME=${{ matrix.patch-assetname }}
|
||||
[ -n "$_NAME" ] || _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
|
||||
|
||||
- name: Build Xray
|
||||
run: |
|
||||
mkdir -p build_assets
|
||||
make
|
||||
find . -maxdepth 1 -type f -regex './\(wxray\|xray\|xray_softfloat\)\(\|.exe\)' -exec mv {} ./build_assets/ \;
|
||||
COMMID=$(git describe --always --dirty)
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Building Xray for Windows...'
|
||||
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
|
||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
else
|
||||
echo 'Building Xray...'
|
||||
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
|
||||
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
echo 'Building soft-float Xray for MIPS/MIPSLE 32-bit...'
|
||||
GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
else
|
||||
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v4
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
if: matrix.goos == 'windows'
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
|
||||
- name: Add additional assets into package
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == '386' ]]; then
|
||||
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == 'arm' ]]; then
|
||||
mv resources/wintun/bin/arm/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
run: |
|
||||
mv -f resources/* build_assets
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
|
||||
@@ -212,17 +276,6 @@ jobs:
|
||||
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
|
||||
done
|
||||
|
||||
- name: Change the name
|
||||
run: |
|
||||
mv build_assets Xray-${{ env.ASSET_NAME }}
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Xray-${{ env.ASSET_NAME }}
|
||||
path: |
|
||||
./Xray-${{ env.ASSET_NAME }}/*
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: github.event_name == 'release'
|
||||
@@ -231,3 +284,10 @@ jobs:
|
||||
file: ./Xray-${{ env.ASSET_NAME }}.zip*
|
||||
tag: ${{ github.ref }}
|
||||
file_glob: true
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Xray-${{ env.ASSET_NAME }}
|
||||
path: |
|
||||
./build_assets/*
|
||||
|
||||
129
.github/workflows/scheduled-assets-update.yml
vendored
Normal file
129
.github/workflows/scheduled-assets-update.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Scheduled assets update
|
||||
|
||||
# NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a
|
||||
# routine manner, for example: GeoIP/GeoSite.
|
||||
# Currently updating:
|
||||
# - Geodat (GeoIP/Geosite)
|
||||
# - Wintun (wintun.dll)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Update GeoData on every day (22:30 UTC)
|
||||
- cron: "30 22 * * *"
|
||||
push:
|
||||
# Prevent triggering update request storm
|
||||
paths:
|
||||
- ".github/workflows/scheduled-assets-update.yml"
|
||||
pull_request:
|
||||
# Prevent triggering update request storm
|
||||
paths:
|
||||
- ".github/workflows/scheduled-assets-update.yml"
|
||||
|
||||
jobs:
|
||||
geodat:
|
||||
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Update Geodat
|
||||
id: update
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
max_attempts: 60
|
||||
command: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
||||
FILE_NAME="${INFO[3]}.dat"
|
||||
echo -e "Verifying HASH key..."
|
||||
HASH="$(curl -sL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
||||
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
|
||||
continue
|
||||
else
|
||||
echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
|
||||
curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME}
|
||||
echo -e "Verifying HASH key..."
|
||||
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Save Geodat Cache
|
||||
uses: actions/cache/save@v5
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
wintun:
|
||||
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
|
||||
- name: Force downloading if run manually or on file update
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
run: |
|
||||
echo "FORCE_UPDATE=true" >> $GITHUB_ENV
|
||||
|
||||
- name: Update Wintun
|
||||
id: update
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
max_attempts: 60
|
||||
command: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('amd64' 'x86' 'arm64' 'arm')
|
||||
for ARCHITECTURE in "${LIST[@]}"
|
||||
do
|
||||
FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll"
|
||||
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists"
|
||||
continue
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
|
||||
missing=true
|
||||
fi
|
||||
done
|
||||
if [ -s "./resources/wintun/LICENSE.txt" ]; then
|
||||
echo -e "LICENSE for Wintun exists"
|
||||
else
|
||||
echo -e "LICENSE for Wintun is missing"
|
||||
missing=true
|
||||
fi
|
||||
if [[ -v FORCE_UPDATE ]]; then
|
||||
missing=true
|
||||
fi
|
||||
if [[ "$missing" == true ]]; then
|
||||
FILENAME=wintun.zip
|
||||
DOWNLOAD_FILE=wintun-0.14.1.zip
|
||||
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
|
||||
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Save Wintun Cache
|
||||
uses: actions/cache/save@v5
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-${{ github.sha }}-${{ github.run_number }}
|
||||
49
.github/workflows/test.yml
vendored
49
.github/workflows/test.yml
vendored
@@ -2,23 +2,40 @@ name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
- name: Check Assets Existence
|
||||
id: check-assets
|
||||
run: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('geoip.dat' 'geosite.dat')
|
||||
for FILE_NAME in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking ${FILE_NAME}..."
|
||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||
echo -e "${FILE_NAME} exists."
|
||||
else
|
||||
echo -e "${FILE_NAME} does not exist."
|
||||
echo "missing=true" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
done
|
||||
- name: Sleep for 90 seconds if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
run: sleep 90
|
||||
|
||||
test:
|
||||
needs: check-assets
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -28,14 +45,14 @@ jobs:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v4
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
50
.gitignore
vendored
50
.gitignore
vendored
@@ -11,13 +11,23 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
# macOS specific files
|
||||
.DS_Store
|
||||
|
||||
*.DS_Store
|
||||
.idea
|
||||
# IDE/editor specific files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Archives and compressed files
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.tar
|
||||
*.gz
|
||||
*.bz2
|
||||
|
||||
# Go build binaries
|
||||
xray
|
||||
xray_softfloat
|
||||
mockgen
|
||||
@@ -26,5 +36,33 @@ vprotogen
|
||||
errorgen
|
||||
!common/errors/errorgen/
|
||||
*.dat
|
||||
.vscode
|
||||
/build_assets
|
||||
|
||||
# Build assets
|
||||
/build_assets/
|
||||
|
||||
# Output from dlv test
|
||||
**/debug.*
|
||||
|
||||
# Certificates and keys
|
||||
*.crt
|
||||
*.key
|
||||
|
||||
# Dependency directories (uncomment if needed)
|
||||
# vendor/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Coverage reports
|
||||
coverage.*
|
||||
|
||||
# Node modules (in case of frontend assets)
|
||||
node_modules/
|
||||
|
||||
# System files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Other common ignores
|
||||
*.bak
|
||||
*.tmp
|
||||
|
||||
37
Makefile
37
Makefile
@@ -1,37 +0,0 @@
|
||||
NAME = xray
|
||||
|
||||
VERSION=$(shell git describe --always --dirty)
|
||||
|
||||
# NOTE: This MAKEFILE can be used to build Xray-core locally and in Automatic workflows. It is \
|
||||
provided for convinience in automatic building and functions as a part of it.
|
||||
# NOTE: If you need to modify this file, please be aware that:\
|
||||
- This file is not the main Makefile; it only accepts environment variables and builds the \
|
||||
binary.\
|
||||
- Automatic building expects the correct binaries to be built by this Makefile. If you \
|
||||
intend to propose a change to this Makefile, carefully review the file below and ensure \
|
||||
that the change will not accidently break the automatic building:\
|
||||
.github/workflows/release.yml \
|
||||
Otherwise it is recommended to contact the project maintainers.
|
||||
|
||||
LDFLAGS = -X github.com/xtls/xray-core/core.build=$(VERSION) -s -w -buildid=
|
||||
PARAMS = -trimpath -ldflags "$(LDFLAGS)" -v
|
||||
MAIN = ./main
|
||||
PREFIX ?= $(shell go env GOPATH)
|
||||
ifeq ($(GOOS),windows)
|
||||
OUTPUT = $(NAME).exe
|
||||
ADDITION = go build -o w$(NAME).exe -trimpath -ldflags "-H windowsgui $(LDFLAGS)" -v $(MAIN)
|
||||
else
|
||||
OUTPUT = $(NAME)
|
||||
endif
|
||||
ifeq ($(shell echo "$(GOARCH)" | grep -Eq "(mips|mipsle)" && echo true),true) #
|
||||
ADDITION = GOMIPS=softfloat go build -o $(NAME)_softfloat -trimpath -ldflags "$(LDFLAGS)" -v $(MAIN)
|
||||
endif
|
||||
.PHONY: clean build
|
||||
|
||||
build:
|
||||
go build -o $(OUTPUT) $(PARAMS) $(MAIN)
|
||||
$(ADDITION)
|
||||
|
||||
clean:
|
||||
go clean -v -i $(PWD)
|
||||
rm -f xray xray.exe wxray.exe xray_softfloat
|
||||
105
README.md
105
README.md
@@ -4,6 +4,31 @@
|
||||
|
||||
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
||||
|
||||
## Sponsors
|
||||
|
||||
[](https://docs.rw)
|
||||
|
||||
[](https://happ.su)
|
||||
|
||||
[**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668)
|
||||
|
||||
## Donation & NFTs
|
||||
|
||||
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||
|
||||
[<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||
|
||||
- **TRX(Tron)/USDT/USDC: `TNrDh5VSfwd4RPrwsohr6poyNTfFefNYan`**
|
||||
- **TON: `UQApeV-u2gm43aC1uP76xAC1m6vCylstaN1gpfBmre_5IyTH`**
|
||||
- **BTC: `1JpqcziZZuqv3QQJhZGNGBVdCBrGgkL6cT`**
|
||||
- **XMR: `4ABHQZ3yJZkBnLoqiKvb3f8eqUnX4iMPb6wdant5ZLGQELctcerceSGEfJnoCk6nnyRZm73wrwSgvZ2WmjYLng6R7sR67nq`**
|
||||
- **SOL/USDT/USDC: `3x5NuXHzB5APG6vRinPZcsUv5ukWUY1tBGRSJiEJWtZa`**
|
||||
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
||||
- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**
|
||||
- **VLESS NFT: https://opensea.io/collection/vless**
|
||||
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
|
||||
- **Related links: [VLESS Post-Quantum Encryption](https://github.com/XTLS/Xray-core/pull/5067), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113), [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**
|
||||
|
||||
## License
|
||||
|
||||
[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE)
|
||||
@@ -18,6 +43,10 @@
|
||||
|
||||
[Project X Channel](https://t.me/projectXtls)
|
||||
|
||||
[Project VLESS](https://t.me/projectVless) (Русский)
|
||||
|
||||
[Project XHTTP](https://t.me/projectXhttp) (Persian)
|
||||
|
||||
## Installation
|
||||
|
||||
- Linux Script
|
||||
@@ -26,15 +55,19 @@
|
||||
- Docker
|
||||
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
|
||||
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
|
||||
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
|
||||
- Web Panel
|
||||
- [X-UI-English](https://github.com/NidukaAkalanka/x-ui-english), [3X-UI](https://github.com/MHSanaei/3x-ui), [X-UI](https://github.com/alireza0/x-ui), [X-UI](https://github.com/diditra/x-ui)
|
||||
- [Xray-UI](https://github.com/qist/xray-ui), [X-UI](https://github.com/sing-web/x-ui)
|
||||
- [Hiddify](https://github.com/hiddify/hiddify-config)
|
||||
- [Remnawave](https://github.com/remnawave/panel)
|
||||
- [3X-UI](https://github.com/MHSanaei/3x-ui)
|
||||
- [PasarGuard](https://github.com/PasarGuard/panel)
|
||||
- [Xray-UI](https://github.com/qist/xray-ui)
|
||||
- [X-Panel](https://github.com/xeefei/X-Panel)
|
||||
- [Marzban](https://github.com/Gozargah/Marzban)
|
||||
- [Libertea](https://github.com/VZiChoushaDui/Libertea)
|
||||
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
|
||||
- [TX-UI](https://github.com/AghayeCoder/tx-ui)
|
||||
- One Click
|
||||
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
|
||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool)
|
||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||
- Magisk
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
@@ -65,83 +98,101 @@
|
||||
- [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2)
|
||||
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
|
||||
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||
- Asuswrt-Merlin
|
||||
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
|
||||
- Windows
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [NekoRay](https://github.com/Matsuridayo/nekoray)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
- [HiddifyN](https://github.com/hiddify/HiddifyN)
|
||||
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- Android
|
||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||
- [HiddifyNG](https://github.com/hiddify/HiddifyNG)
|
||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||
- iOS & macOS arm64
|
||||
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
||||
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- macOS arm64 & x64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [GoXRay](https://github.com/goxray/desktop)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- Linux
|
||||
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||
- [NekoRay](https://github.com/Matsuridayo/nekoray)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
- [GorzRay](https://github.com/ketetefid/GorzRay)
|
||||
- [GoXRay](https://github.com/goxray/desktop)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
- iOS & macOS arm64
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||
- Xray Tools
|
||||
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
|
||||
- [xray-checker](https://github.com/kutovoys/xray-checker)
|
||||
- Xray Wrapper
|
||||
- [XTLS/libXray](https://github.com/XTLS/libXray)
|
||||
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
||||
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||
- [XrayKit](https://github.com/arror/XrayKit)
|
||||
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
||||
- [xray-api](https://github.com/XVGuardian/xray-api)
|
||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
|
||||
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
|
||||
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||
- [Clash Verge](https://github.com/zzzgydi/clash-verge)
|
||||
- [clashN](https://github.com/2dust/clashN)
|
||||
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
|
||||
- [meta_for_ios](https://t.me/meta_for_ios)
|
||||
- [sing-box](https://github.com/SagerNet/sing-box)
|
||||
- [installReality](https://github.com/BoxXt/installReality)
|
||||
- [sbox-reality](https://github.com/Misaka-blog/sbox-reality)
|
||||
- [sing-box-for-ios](https://github.com/SagerNet/sing-box-for-ios)
|
||||
- Cores
|
||||
- [Amnezia VPN](https://github.com/amnezia-vpn)
|
||||
- [mihomo](https://github.com/MetaCubeX/mihomo)
|
||||
- [sing-box](https://github.com/SagerNet/sing-box)
|
||||
|
||||
## Contributing
|
||||
|
||||
[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)
|
||||
|
||||
[](https://deepwiki.com/XTLS/Xray-core)
|
||||
|
||||
## Credits
|
||||
|
||||
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
|
||||
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
|
||||
|
||||
## Compilation
|
||||
## One-line Compilation
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
$env:CGO_ENABLED=0
|
||||
go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main
|
||||
go build -o xray.exe -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
|
||||
```
|
||||
|
||||
### Linux / macOS
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
|
||||
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
|
||||
```
|
||||
|
||||
### Reproducible Releases
|
||||
|
||||
Make sure that you are using the same Go version, and remember to set the git commit id (7 bytes):
|
||||
|
||||
```bash
|
||||
make
|
||||
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
|
||||
```
|
||||
|
||||
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
|
||||
```
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package commander
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
core "github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
@@ -46,7 +45,7 @@ func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
|
||||
}
|
||||
service, ok := rawService.(Service)
|
||||
if !ok {
|
||||
return nil, newError("not a Service.")
|
||||
return nil, errors.New("not a Service.")
|
||||
}
|
||||
c.services = append(c.services, service)
|
||||
}
|
||||
@@ -70,16 +69,16 @@ func (c *Commander) Start() error {
|
||||
|
||||
var listen = func(listener net.Listener) {
|
||||
if err := c.server.Serve(listener); err != nil {
|
||||
newError("failed to start grpc server").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.listen) > 0 {
|
||||
if l, err := net.Listen("tcp", c.listen); err != nil {
|
||||
newError("API server failed to listen on ", c.listen).Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||
return err
|
||||
} else {
|
||||
newError("API server listening on ", l.Addr()).AtInfo().WriteToLog()
|
||||
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
||||
go listen(l)
|
||||
}
|
||||
return nil
|
||||
@@ -93,7 +92,7 @@ func (c *Commander) Start() error {
|
||||
go listen(listener)
|
||||
|
||||
if err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil {
|
||||
newError("failed to remove existing handler").WriteToLog()
|
||||
errors.LogInfoInner(context.Background(), err, "failed to remove existing handler")
|
||||
}
|
||||
|
||||
return c.ohm.AddHandler(context.Background(), &Outbound{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/commander/config.proto
|
||||
|
||||
package commander
|
||||
@@ -38,11 +38,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_commander_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_commander_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -53,7 +51,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_commander_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -98,11 +96,9 @@ type ReflectionConfig struct {
|
||||
|
||||
func (x *ReflectionConfig) Reset() {
|
||||
*x = ReflectionConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_commander_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_commander_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ReflectionConfig) String() string {
|
||||
@@ -113,7 +109,7 @@ func (*ReflectionConfig) ProtoMessage() {}
|
||||
|
||||
func (x *ReflectionConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_commander_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -166,7 +162,7 @@ func file_app_commander_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_commander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_app_commander_config_proto_goTypes = []interface{}{
|
||||
var file_app_commander_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.app.commander.Config
|
||||
(*ReflectionConfig)(nil), // 1: xray.app.commander.ReflectionConfig
|
||||
(*serial.TypedMessage)(nil), // 2: xray.common.serial.TypedMessage
|
||||
@@ -185,32 +181,6 @@ func file_app_commander_config_proto_init() {
|
||||
if File_app_commander_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_commander_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_commander_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReflectionConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package commander
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
@@ -31,7 +33,7 @@ func (l *OutboundListener) add(conn net.Conn) {
|
||||
func (l *OutboundListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case <-l.done.Wait():
|
||||
return nil, newError("listen closed")
|
||||
return nil, errors.New("listen closed")
|
||||
case c := <-l.buffer:
|
||||
return c, nil
|
||||
}
|
||||
@@ -107,3 +109,13 @@ func (co *Outbound) Close() error {
|
||||
co.closed = true
|
||||
return co.listener.Close()
|
||||
}
|
||||
|
||||
// SenderSettings implements outbound.Handler.
|
||||
func (co *Outbound) SenderSettings() *serial.TypedMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxySettings implements outbound.Handler.
|
||||
func (co *Outbound) ProxySettings() *serial.TypedMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/dispatcher/config.proto
|
||||
|
||||
package dispatcher
|
||||
@@ -28,11 +28,9 @@ type SessionConfig struct {
|
||||
|
||||
func (x *SessionConfig) Reset() {
|
||||
*x = SessionConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SessionConfig) String() string {
|
||||
@@ -43,7 +41,7 @@ func (*SessionConfig) ProtoMessage() {}
|
||||
|
||||
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -68,11 +66,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -83,7 +79,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dispatcher_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -139,7 +135,7 @@ func file_app_dispatcher_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_app_dispatcher_config_proto_goTypes = []interface{}{
|
||||
var file_app_dispatcher_config_proto_goTypes = []any{
|
||||
(*SessionConfig)(nil), // 0: xray.app.dispatcher.SessionConfig
|
||||
(*Config)(nil), // 1: xray.app.dispatcher.Config
|
||||
}
|
||||
@@ -157,32 +153,6 @@ func file_app_dispatcher_config_proto_init() {
|
||||
if File_app_dispatcher_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_dispatcher_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SessionConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dispatcher_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package dispatcher
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
@@ -11,6 +9,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
@@ -26,25 +25,29 @@ import (
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
)
|
||||
|
||||
var errSniffingTimeout = newError("timeout on sniffing")
|
||||
var errSniffingTimeout = errors.New("timeout on sniffing")
|
||||
|
||||
type cachedReader struct {
|
||||
sync.Mutex
|
||||
reader *pipe.Reader
|
||||
reader buf.TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader
|
||||
cache buf.MultiBuffer
|
||||
}
|
||||
|
||||
func (r *cachedReader) Cache(b *buf.Buffer) {
|
||||
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
|
||||
func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
|
||||
mb, err := r.reader.ReadMultiBufferTimeout(deadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Lock()
|
||||
if !mb.IsEmpty() {
|
||||
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
||||
}
|
||||
b.Clear()
|
||||
rawBytes := b.Extend(buf.Size)
|
||||
rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
|
||||
n := r.cache.Copy(rawBytes)
|
||||
b.Resize(0, int32(n))
|
||||
r.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
||||
@@ -84,7 +87,9 @@ func (r *cachedReader) Interrupt() {
|
||||
r.cache = buf.ReleaseMulti(r.cache)
|
||||
}
|
||||
r.Unlock()
|
||||
r.reader.Interrupt()
|
||||
if p, ok := r.reader.(*pipe.Reader); ok {
|
||||
p.Interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDispatcher is a default implementation of Dispatcher.
|
||||
@@ -93,7 +98,6 @@ type DefaultDispatcher struct {
|
||||
router routing.Router
|
||||
policy policy.Manager
|
||||
stats stats.Manager
|
||||
dns dns.Client
|
||||
fdns dns.FakeDNSEngine
|
||||
}
|
||||
|
||||
@@ -101,10 +105,10 @@ func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
d := new(DefaultDispatcher)
|
||||
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
||||
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
d.fdns = fdns
|
||||
})
|
||||
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
||||
return d.Init(config.(*Config), om, router, pm, sm)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,12 +117,11 @@ func init() {
|
||||
}
|
||||
|
||||
// Init initializes DefaultDispatcher.
|
||||
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error {
|
||||
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
|
||||
d.ohm = om
|
||||
d.router = router
|
||||
d.policy = pm
|
||||
d.stats = sm
|
||||
d.dns = dns
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,11 +179,64 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.Stats.UserOnline {
|
||||
name := "user>>>" + user.Email + ">>>online"
|
||||
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
|
||||
sessionInbounds := session.InboundFromContext(ctx)
|
||||
userIP := sessionInbounds.Source.Address.String()
|
||||
om.AddIP(userIP)
|
||||
// log Online user with ips
|
||||
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inboundLink, outboundLink
|
||||
}
|
||||
|
||||
func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager stats.Manager, link *transport.Link) *transport.Link {
|
||||
sessionInbound := session.InboundFromContext(ctx)
|
||||
var user *protocol.MemoryUser
|
||||
if sessionInbound != nil {
|
||||
user = sessionInbound.User
|
||||
}
|
||||
|
||||
link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}
|
||||
|
||||
if user != nil && len(user.Email) > 0 {
|
||||
p := policyManager.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
||||
}
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||
link.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: link.Writer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.Stats.UserOnline {
|
||||
name := "user>>>" + user.Email + ">>>online"
|
||||
if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {
|
||||
sessionInbounds := session.InboundFromContext(ctx)
|
||||
userIP := sessionInbounds.Source.Address.String()
|
||||
om.AddIP(userIP)
|
||||
// log Online user with ips
|
||||
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||
domain := result.Domain()
|
||||
if domain == "" {
|
||||
@@ -191,7 +247,7 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
||||
pattern := d[7:]
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
newError("Unable to compile regex").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "Unable to compile regex")
|
||||
continue
|
||||
}
|
||||
if re.MatchString(domain) {
|
||||
@@ -213,7 +269,7 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
||||
}
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||
fkr0.IsIPInIPPool(destination.Address) {
|
||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "Using sniffer ", protocolString, " since the fake DNS missed")
|
||||
return true
|
||||
}
|
||||
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||
@@ -261,7 +317,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
protocol := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
@@ -286,7 +342,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
// DispatchLink implements routing.Dispatcher.
|
||||
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||
if !destination.IsValid() {
|
||||
return newError("Dispatcher: Invalid destination.")
|
||||
return errors.New("Dispatcher: Invalid destination.")
|
||||
}
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if len(outbounds) == 0 {
|
||||
@@ -301,12 +357,13 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
content = new(session.Content)
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
}
|
||||
outbound = WrapLink(ctx, d.policy, d.stats, outbound)
|
||||
sniffingRequest := content.SniffingRequest
|
||||
if !sniffingRequest.Enabled {
|
||||
d.routedDispatch(ctx, outbound, destination)
|
||||
} else {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
reader: outbound.Reader.(buf.TimeoutReader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
@@ -315,7 +372,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
protocol := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
@@ -338,7 +395,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
}
|
||||
|
||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||
payload := buf.New()
|
||||
payload := buf.NewWithSize(32767)
|
||||
defer payload.Release()
|
||||
|
||||
sniffer := NewSniffer(ctx)
|
||||
@@ -350,26 +407,36 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
|
||||
}
|
||||
|
||||
contentResult, contentErr := func() (SniffResult, error) {
|
||||
cacheDeadline := 200 * time.Millisecond
|
||||
totalAttempt := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
totalAttempt++
|
||||
if totalAttempt > 2 {
|
||||
return nil, errSniffingTimeout
|
||||
cachingStartingTimeStamp := time.Now()
|
||||
err := cReader.Cache(payload, cacheDeadline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
|
||||
cacheDeadline -= cachingTimeElapsed
|
||||
|
||||
cReader.Cache(payload)
|
||||
if !payload.IsEmpty() {
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||
if err != common.ErrNoClue {
|
||||
switch err {
|
||||
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
|
||||
totalAttempt++
|
||||
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
|
||||
// in this case, do not add totalAttempt(allow to read until timeout)
|
||||
default:
|
||||
return result, err
|
||||
}
|
||||
} else {
|
||||
totalAttempt++
|
||||
}
|
||||
if payload.IsFull() {
|
||||
return nil, errUnknownContent
|
||||
if totalAttempt >= 2 || cacheDeadline <= 0 {
|
||||
return nil, errSniffingTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,21 +449,10 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
|
||||
}
|
||||
return contentResult, contentErr
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||
proxied := hosts.LookupHosts(ob.Target.String())
|
||||
if proxied != nil {
|
||||
ro := ob.RouteTarget == destination
|
||||
destination.Address = *proxied
|
||||
if ro {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var handler outbound.Handler
|
||||
|
||||
@@ -407,10 +463,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||
isPickRoute = 1
|
||||
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]")
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogError(ctx, "non existing tag for platform initialized detour: ", forcedOutboundTag)
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
@@ -420,13 +476,20 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
outTag := route.GetOutboundTag()
|
||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||
isPickRoute = 2
|
||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
if route.GetRuleTag() == "" {
|
||||
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
|
||||
} else {
|
||||
errors.LogInfo(ctx, "Hit route rule: [", route.GetRuleTag(), "] so taking detour [", outTag, "] for [", destination, "]")
|
||||
}
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogWarning(ctx, "non existing outTag: ", outTag)
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return // DO NOT CHANGE: the traffic shouldn't be processed by default outbound if the specified outbound tag doesn't exist (yet), e.g., VLESS Reverse Proxy
|
||||
}
|
||||
} else {
|
||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "default route for ", destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +498,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "default outbound handler not exist")
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
package dispatcher
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package dispatcher
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -22,16 +23,16 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
||||
}
|
||||
|
||||
if fakeDNSEngine == nil {
|
||||
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||
return protocolSnifferWithMetadata{}, errNotInit
|
||||
}
|
||||
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds) - 1]
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
if ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP {
|
||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)
|
||||
if domainFromFakeDNS != "" {
|
||||
newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String())
|
||||
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||
}
|
||||
}
|
||||
@@ -109,10 +110,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
|
||||
}
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
|
||||
errors.LogDebug(ctx, "ip address not in fake dns range, return as is")
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
|
||||
errors.LogWarning(ctx, "fake dns sniffer did not set address in range option, assume false.")
|
||||
return nil, common.ErrNoClue
|
||||
},
|
||||
metadataSniffer: false,
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/protocol/quic"
|
||||
@@ -34,7 +36,7 @@ type Sniffer struct {
|
||||
func NewSniffer(ctx context.Context) *Sniffer {
|
||||
ret := &Sniffer{
|
||||
sniffer: []protocolSnifferWithMetadata{
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b, c) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||
@@ -52,19 +54,22 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
||||
return ret
|
||||
}
|
||||
|
||||
var errUnknownContent = newError("unknown content")
|
||||
var errUnknownContent = errors.New("unknown content")
|
||||
|
||||
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||
var pendingSniffer []protocolSnifferWithMetadata
|
||||
for _, si := range s.sniffer {
|
||||
s := si.protocolSniffer
|
||||
protocolSniffer := si.protocolSniffer
|
||||
if si.metadataSniffer || si.network != network {
|
||||
continue
|
||||
}
|
||||
result, err := s(c, payload)
|
||||
result, err := protocolSniffer(c, payload)
|
||||
if err == common.ErrNoClue {
|
||||
pendingSniffer = append(pendingSniffer, si)
|
||||
continue
|
||||
} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing
|
||||
s.sniffer = []protocolSnifferWithMetadata{si}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil && result != nil {
|
||||
|
||||
339
app/dns/cache_controller.go
Normal file
339
app/dns/cache_controller.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
go_errors "errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
const (
|
||||
minSizeForEmptyRebuild = 512
|
||||
shrinkAbsoluteThreshold = 10240
|
||||
shrinkRatioThreshold = 0.65
|
||||
migrationBatchSize = 4096
|
||||
)
|
||||
|
||||
type CacheController struct {
|
||||
name string
|
||||
disableCache bool
|
||||
serveStale bool
|
||||
serveExpiredTTL int32
|
||||
|
||||
ips map[string]*record
|
||||
dirtyips map[string]*record
|
||||
|
||||
sync.RWMutex
|
||||
pub *pubsub.Service
|
||||
cacheCleanup *task.Periodic
|
||||
highWatermark int
|
||||
requestGroup singleflight.Group
|
||||
}
|
||||
|
||||
func NewCacheController(name string, disableCache bool, serveStale bool, serveExpiredTTL uint32) *CacheController {
|
||||
c := &CacheController{
|
||||
name: name,
|
||||
disableCache: disableCache,
|
||||
serveStale: serveStale,
|
||||
serveExpiredTTL: -int32(serveExpiredTTL),
|
||||
ips: make(map[string]*record),
|
||||
pub: pubsub.NewService(),
|
||||
}
|
||||
|
||||
c.cacheCleanup = &task.Periodic{
|
||||
Interval: 300 * time.Second,
|
||||
Execute: c.CacheCleanup,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// CacheCleanup clears expired items from cache
|
||||
func (c *CacheController) CacheCleanup() error {
|
||||
expiredKeys, err := c.collectExpiredKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(expiredKeys) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.writeAndShrink(expiredKeys)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheController) collectExpiredKeys() ([]string, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if len(c.ips) == 0 {
|
||||
return nil, errors.New("nothing to do. stopping...")
|
||||
}
|
||||
|
||||
// skip collection if a migration is in progress
|
||||
if c.dirtyips != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if c.serveStale && c.serveExpiredTTL != 0 {
|
||||
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
|
||||
}
|
||||
|
||||
expiredKeys := make([]string, 0, len(c.ips)/4) // pre-allocate
|
||||
|
||||
for domain, rec := range c.ips {
|
||||
if (rec.A != nil && rec.A.Expire.Before(now)) ||
|
||||
(rec.AAAA != nil && rec.AAAA.Expire.Before(now)) {
|
||||
expiredKeys = append(expiredKeys, domain)
|
||||
}
|
||||
}
|
||||
|
||||
return expiredKeys, nil
|
||||
}
|
||||
|
||||
func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
// double check to prevent upper call multiple cleanup tasks
|
||||
if c.dirtyips != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lenBefore := len(c.ips)
|
||||
if lenBefore > c.highWatermark {
|
||||
c.highWatermark = lenBefore
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if c.serveStale && c.serveExpiredTTL != 0 {
|
||||
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
|
||||
}
|
||||
|
||||
for _, domain := range expiredKeys {
|
||||
rec := c.ips[domain]
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
if rec.A != nil && rec.A.Expire.Before(now) {
|
||||
rec.A = nil
|
||||
}
|
||||
if rec.AAAA != nil && rec.AAAA.Expire.Before(now) {
|
||||
rec.AAAA = nil
|
||||
}
|
||||
if rec.A == nil && rec.AAAA == nil {
|
||||
delete(c.ips, domain)
|
||||
}
|
||||
}
|
||||
|
||||
lenAfter := len(c.ips)
|
||||
|
||||
if lenAfter == 0 {
|
||||
if c.highWatermark >= minSizeForEmptyRebuild {
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
" rebuilding empty cache map to reclaim memory.",
|
||||
" size_before_cleanup=", lenBefore,
|
||||
" peak_size_before_rebuild=", c.highWatermark,
|
||||
)
|
||||
|
||||
c.ips = make(map[string]*record)
|
||||
c.highWatermark = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
||||
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
" shrinking cache map to reclaim memory.",
|
||||
" new_size=", lenAfter,
|
||||
" peak_size_before_shrink=", c.highWatermark,
|
||||
" reduction_since_peak=", reductionFromPeak,
|
||||
)
|
||||
|
||||
c.dirtyips = c.ips
|
||||
c.ips = make(map[string]*record, int(float64(lenAfter)*1.1))
|
||||
c.highWatermark = lenAfter
|
||||
go c.migrate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type migrationEntry struct {
|
||||
key string
|
||||
value *record
|
||||
}
|
||||
|
||||
func (c *CacheController) migrate() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errors.LogError(context.Background(), c.name, " panic during cache migration: ", r)
|
||||
c.Lock()
|
||||
c.dirtyips = nil
|
||||
// c.ips = make(map[string]*record)
|
||||
// c.highWatermark = 0
|
||||
c.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
c.RLock()
|
||||
dirtyips := c.dirtyips
|
||||
c.RUnlock()
|
||||
|
||||
// double check to prevent upper call multiple cleanup tasks
|
||||
if dirtyips == nil {
|
||||
return
|
||||
}
|
||||
|
||||
errors.LogDebug(context.Background(), c.name, " starting background cache migration for ", len(dirtyips), " items")
|
||||
|
||||
batch := make([]migrationEntry, 0, migrationBatchSize)
|
||||
for domain, recD := range dirtyips {
|
||||
batch = append(batch, migrationEntry{domain, recD})
|
||||
|
||||
if len(batch) >= migrationBatchSize {
|
||||
c.flush(batch)
|
||||
batch = batch[:0]
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
if len(batch) > 0 {
|
||||
c.flush(batch)
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
c.dirtyips = nil
|
||||
c.Unlock()
|
||||
|
||||
errors.LogDebug(context.Background(), c.name, " cache migration completed")
|
||||
}
|
||||
|
||||
func (c *CacheController) flush(batch []migrationEntry) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for _, dirty := range batch {
|
||||
if cur := c.ips[dirty.key]; cur != nil {
|
||||
merge := &record{}
|
||||
if cur.A == nil {
|
||||
merge.A = dirty.value.A
|
||||
} else {
|
||||
merge.A = cur.A
|
||||
}
|
||||
if cur.AAAA == nil {
|
||||
merge.AAAA = dirty.value.AAAA
|
||||
} else {
|
||||
merge.AAAA = cur.AAAA
|
||||
}
|
||||
c.ips[dirty.key] = merge
|
||||
} else {
|
||||
c.ips[dirty.key] = dirty.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheController) updateRecord(req *dnsRequest, rep *IPRecord) {
|
||||
rtt := time.Since(req.start)
|
||||
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
c.pub.Publish(req.domain+"4", rep)
|
||||
case dnsmessage.TypeAAAA:
|
||||
c.pub.Publish(req.domain+"6", rep)
|
||||
}
|
||||
|
||||
if c.disableCache {
|
||||
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt)
|
||||
return
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
lockWait := time.Since(req.start) - rtt
|
||||
|
||||
newRec := &record{}
|
||||
oldRec := c.ips[req.domain]
|
||||
var dirtyRec *record
|
||||
if c.dirtyips != nil {
|
||||
dirtyRec = c.dirtyips[req.domain]
|
||||
}
|
||||
|
||||
var pubRecord *IPRecord
|
||||
var pubSuffix string
|
||||
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
newRec.A = rep
|
||||
if oldRec != nil && oldRec.AAAA != nil {
|
||||
newRec.AAAA = oldRec.AAAA
|
||||
pubRecord = oldRec.AAAA
|
||||
} else if dirtyRec != nil && dirtyRec.AAAA != nil {
|
||||
pubRecord = dirtyRec.AAAA
|
||||
}
|
||||
pubSuffix = "6"
|
||||
case dnsmessage.TypeAAAA:
|
||||
newRec.AAAA = rep
|
||||
if oldRec != nil && oldRec.A != nil {
|
||||
newRec.A = oldRec.A
|
||||
pubRecord = oldRec.A
|
||||
} else if dirtyRec != nil && dirtyRec.A != nil {
|
||||
pubRecord = dirtyRec.A
|
||||
}
|
||||
pubSuffix = "4"
|
||||
}
|
||||
|
||||
c.ips[req.domain] = newRec
|
||||
c.Unlock()
|
||||
|
||||
if pubRecord != nil {
|
||||
_, ttl, err := pubRecord.getIPs()
|
||||
if ttl > 0 && !go_errors.Is(err, errRecordNotFound) {
|
||||
c.pub.Publish(req.domain+pubSuffix, pubRecord)
|
||||
}
|
||||
}
|
||||
|
||||
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt, ", lock: ", lockWait)
|
||||
|
||||
if !c.serveStale || c.serveExpiredTTL != 0 {
|
||||
common.Must(c.cacheCleanup.Start())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheController) findRecords(domain string) *record {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
rec := c.ips[domain]
|
||||
if rec == nil && c.dirtyips != nil {
|
||||
rec = c.dirtyips[domain]
|
||||
}
|
||||
return rec
|
||||
}
|
||||
|
||||
func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
||||
// ipv4 and ipv6 belong to different subscription groups
|
||||
if option.IPv4Enable {
|
||||
sub4 = c.pub.Subscribe(domain + "4")
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
sub6 = c.pub.Subscribe(domain + "6")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func closeSubscribers(sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
||||
if sub4 != nil {
|
||||
sub4.Close()
|
||||
}
|
||||
if sub6 != nil {
|
||||
sub6.Close()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
@@ -36,11 +37,11 @@ var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||
func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
|
||||
strMType, f := typeMap[t]
|
||||
if !f {
|
||||
return nil, newError("unknown mapping type", t).AtWarning()
|
||||
return nil, errors.New("unknown mapping type", t).AtWarning()
|
||||
}
|
||||
matcher, err := strMType.New(domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create str matcher").Base(err)
|
||||
return nil, errors.New("failed to create str matcher").Base(err)
|
||||
}
|
||||
return matcher, nil
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||
if addr.Family().IsIP() {
|
||||
ips = append(ips, addr.IP())
|
||||
} else {
|
||||
return nil, newError("Failed to convert address", addr, "to Net IP.").AtWarning()
|
||||
return nil, errors.New("Failed to convert address", addr, "to Net IP.").AtWarning()
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/dns/config.proto
|
||||
|
||||
package dns
|
||||
@@ -80,6 +80,7 @@ const (
|
||||
QueryStrategy_USE_IP QueryStrategy = 0
|
||||
QueryStrategy_USE_IP4 QueryStrategy = 1
|
||||
QueryStrategy_USE_IP6 QueryStrategy = 2
|
||||
QueryStrategy_USE_SYS QueryStrategy = 3
|
||||
)
|
||||
|
||||
// Enum value maps for QueryStrategy.
|
||||
@@ -88,11 +89,13 @@ var (
|
||||
0: "USE_IP",
|
||||
1: "USE_IP4",
|
||||
2: "USE_IP6",
|
||||
3: "USE_SYS",
|
||||
}
|
||||
QueryStrategy_value = map[string]int32{
|
||||
"USE_IP": 0,
|
||||
"USE_IP4": 1,
|
||||
"USE_IP6": 2,
|
||||
"USE_SYS": 3,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -132,18 +135,26 @@ type NameServer struct {
|
||||
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
||||
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
||||
ExpectedGeoip []*router.GeoIP `protobuf:"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3" json:"expected_geoip,omitempty"`
|
||||
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||
UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"`
|
||||
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *NameServer) Reset() {
|
||||
*x = NameServer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer) String() string {
|
||||
@@ -154,7 +165,7 @@ func (*NameServer) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -197,9 +208,9 @@ func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetGeoip() []*router.GeoIP {
|
||||
func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
|
||||
if x != nil {
|
||||
return x.Geoip
|
||||
return x.ExpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -218,24 +229,84 @@ func (x *NameServer) GetQueryStrategy() QueryStrategy {
|
||||
return QueryStrategy_USE_IP
|
||||
}
|
||||
|
||||
func (x *NameServer) GetActPrior() bool {
|
||||
if x != nil {
|
||||
return x.ActPrior
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetTag() string {
|
||||
if x != nil {
|
||||
return x.Tag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NameServer) GetTimeoutMs() uint64 {
|
||||
if x != nil {
|
||||
return x.TimeoutMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NameServer) GetDisableCache() bool {
|
||||
if x != nil && x.DisableCache != nil {
|
||||
return *x.DisableCache
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetServeStale() bool {
|
||||
if x != nil && x.ServeStale != nil {
|
||||
return *x.ServeStale
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetServeExpiredTTL() uint32 {
|
||||
if x != nil && x.ServeExpiredTTL != nil {
|
||||
return *x.ServeExpiredTTL
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NameServer) GetFinalQuery() bool {
|
||||
if x != nil {
|
||||
return x.FinalQuery
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
|
||||
if x != nil {
|
||||
return x.UnexpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetActUnprior() bool {
|
||||
if x != nil {
|
||||
return x.ActUnprior
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetPolicyID() uint32 {
|
||||
if x != nil {
|
||||
return x.PolicyID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Nameservers used by this DNS. Only traditional UDP servers are support at
|
||||
// the moment. A special value 'localhost' as a domain address can be set to
|
||||
// use DNS on local system.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in app/dns/config.proto.
|
||||
NameServers []*net.Endpoint `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"`
|
||||
// NameServer list used by this DNS client.
|
||||
// A special value 'localhost' as a domain address can be set to use DNS on local system.
|
||||
NameServer []*NameServer `protobuf:"bytes,5,rep,name=name_server,json=nameServer,proto3" json:"name_server,omitempty"`
|
||||
// Static hosts. Domain to IP.
|
||||
// Deprecated. Use static_hosts.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in app/dns/config.proto.
|
||||
Hosts map[string]*net.IPOrDomain `protobuf:"bytes,2,rep,name=Hosts,proto3" json:"Hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes
|
||||
// (IPv6).
|
||||
ClientIp []byte `protobuf:"bytes,3,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||
@@ -244,18 +315,19 @@ type Config struct {
|
||||
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
// DisableCache disables DNS cache
|
||||
DisableCache bool `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"`
|
||||
ServeStale bool `protobuf:"varint,12,opt,name=serveStale,proto3" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL uint32 `protobuf:"varint,13,opt,name=serveExpiredTTL,proto3" json:"serveExpiredTTL,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
|
||||
DisableFallbackIfMatch bool `protobuf:"varint,11,opt,name=disableFallbackIfMatch,proto3" json:"disableFallbackIfMatch,omitempty"`
|
||||
EnableParallelQuery bool `protobuf:"varint,14,opt,name=enableParallelQuery,proto3" json:"enableParallelQuery,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -266,7 +338,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -281,14 +353,6 @@ func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in app/dns/config.proto.
|
||||
func (x *Config) GetNameServers() []*net.Endpoint {
|
||||
if x != nil {
|
||||
return x.NameServers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetNameServer() []*NameServer {
|
||||
if x != nil {
|
||||
return x.NameServer
|
||||
@@ -296,14 +360,6 @@ func (x *Config) GetNameServer() []*NameServer {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in app/dns/config.proto.
|
||||
func (x *Config) GetHosts() map[string]*net.IPOrDomain {
|
||||
if x != nil {
|
||||
return x.Hosts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetClientIp() []byte {
|
||||
if x != nil {
|
||||
return x.ClientIp
|
||||
@@ -332,6 +388,20 @@ func (x *Config) GetDisableCache() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetServeStale() bool {
|
||||
if x != nil {
|
||||
return x.ServeStale
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetServeExpiredTTL() uint32 {
|
||||
if x != nil {
|
||||
return x.ServeExpiredTTL
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetQueryStrategy() QueryStrategy {
|
||||
if x != nil {
|
||||
return x.QueryStrategy
|
||||
@@ -353,6 +423,13 @@ func (x *Config) GetDisableFallbackIfMatch() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetEnableParallelQuery() bool {
|
||||
if x != nil {
|
||||
return x.EnableParallelQuery
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type NameServer_PriorityDomain struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -364,11 +441,9 @@ type NameServer_PriorityDomain struct {
|
||||
|
||||
func (x *NameServer_PriorityDomain) Reset() {
|
||||
*x = NameServer_PriorityDomain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) String() string {
|
||||
@@ -379,7 +454,7 @@ func (*NameServer_PriorityDomain) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -419,11 +494,9 @@ type NameServer_OriginalRule struct {
|
||||
|
||||
func (x *NameServer_OriginalRule) Reset() {
|
||||
*x = NameServer_OriginalRule{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) String() string {
|
||||
@@ -434,7 +507,7 @@ func (*NameServer_OriginalRule) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -478,11 +551,9 @@ type Config_HostMapping struct {
|
||||
|
||||
func (x *Config_HostMapping) Reset() {
|
||||
*x = Config_HostMapping{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) String() string {
|
||||
@@ -492,8 +563,8 @@ func (x *Config_HostMapping) String() string {
|
||||
func (*Config_HostMapping) ProtoMessage() {}
|
||||
|
||||
func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -505,7 +576,7 @@ func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config_HostMapping.ProtoReflect.Descriptor instead.
|
||||
func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1}
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
||||
@@ -541,107 +612,128 @@ var File_app_dns_config_proto protoreflect.FileDescriptor
|
||||
var file_app_dns_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
||||
0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
|
||||
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69,
|
||||
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70,
|
||||
0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x04, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61,
|
||||
0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b,
|
||||
0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72,
|
||||
0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
|
||||
0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70,
|
||||
0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c,
|
||||
0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52,
|
||||
0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x42,
|
||||
0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||
0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||
0x67, 0x79, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e,
|
||||
0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67,
|
||||
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75,
|
||||
0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xef, 0x05, 0x0a, 0x06, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64,
|
||||
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x12, 0x39, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72,
|
||||
0x79, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61,
|
||||
0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
|
||||
0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67,
|
||||
0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43,
|
||||
0x61, 0x63, 0x68, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74,
|
||||
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72,
|
||||
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79,
|
||||
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61,
|
||||
0x63, 0x6b, 0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c,
|
||||
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62,
|
||||
0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
|
||||
0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70,
|
||||
0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12,
|
||||
0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79,
|
||||
0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09,
|
||||
0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b,
|
||||
0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65,
|
||||
0x78, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61,
|
||||
0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f,
|
||||
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01,
|
||||
0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
|
||||
0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f,
|
||||
0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44,
|
||||
0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
||||
0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x07, 0x0a, 0x0a,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e,
|
||||
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c,
|
||||
0x73, 0x6b, 0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
|
||||
0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f,
|
||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a,
|
||||
0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3d, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x65,
|
||||
0x63, 0x74, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
|
||||
0x65, 0x64, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69,
|
||||
0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e,
|
||||
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e,
|
||||
0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c,
|
||||
0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72,
|
||||
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74,
|
||||
0x50, 0x72, 0x69, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x74,
|
||||
0x50, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x09, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x6f,
|
||||
0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c, 0x64,
|
||||
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x88, 0x01, 0x01, 0x12, 0x23,
|
||||
0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x18, 0x0f, 0x20, 0x01,
|
||||
0x28, 0x08, 0x48, 0x01, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65,
|
||||
0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69,
|
||||
0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0f,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x88,
|
||||
0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79,
|
||||
0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x12, 0x41, 0x0a, 0x10, 0x75, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||
0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47,
|
||||
0x65, 0x6f, 0x49, 0x50, 0x52, 0x0f, 0x75, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||
0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72,
|
||||
0x69, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e,
|
||||
0x70, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49,
|
||||
0x44, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49,
|
||||
0x44, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73,
|
||||
0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54,
|
||||
0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c,
|
||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x69,
|
||||
0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x65,
|
||||
0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x22, 0x98, 0x05,
|
||||
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d,
|
||||
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70,
|
||||
0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73,
|
||||
0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73,
|
||||
0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63,
|
||||
0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64,
|
||||
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x18, 0x0d,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72,
|
||||
0x65, 0x64, 0x54, 0x54, 0x4c, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72,
|
||||
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73,
|
||||
0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62,
|
||||
0x61, 0x63, 0x6b, 0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61,
|
||||
0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c,
|
||||
0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x30, 0x0a, 0x13, 0x65,
|
||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x51, 0x75, 0x65,
|
||||
0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x92, 0x01,
|
||||
0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70,
|
||||
0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x2a,
|
||||
0x42, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
|
||||
0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x53, 0x59,
|
||||
0x53, 0x10, 0x03, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d,
|
||||
0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -657,39 +749,35 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_app_dns_config_proto_goTypes = []interface{}{
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_app_dns_config_proto_goTypes = []any{
|
||||
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
||||
(QueryStrategy)(0), // 1: xray.app.dns.QueryStrategy
|
||||
(*NameServer)(nil), // 2: xray.app.dns.NameServer
|
||||
(*Config)(nil), // 3: xray.app.dns.Config
|
||||
(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain
|
||||
(*NameServer_OriginalRule)(nil), // 5: xray.app.dns.NameServer.OriginalRule
|
||||
nil, // 6: xray.app.dns.Config.HostsEntry
|
||||
(*Config_HostMapping)(nil), // 7: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 8: xray.common.net.Endpoint
|
||||
(*router.GeoIP)(nil), // 9: xray.app.router.GeoIP
|
||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
||||
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
||||
}
|
||||
var file_app_dns_config_proto_depIdxs = []int32{
|
||||
8, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||
4, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
|
||||
9, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP
|
||||
8, // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP
|
||||
5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
||||
1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
8, // 5: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
|
||||
8, // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP
|
||||
2, // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||
6, // 7: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
|
||||
7, // 8: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||
1, // 9: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
0, // 10: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
10, // 11: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
|
||||
0, // 12: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||
1, // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
0, // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_dns_config_proto_init() }
|
||||
@@ -697,75 +785,14 @@ func file_app_dns_config_proto_init() {
|
||||
if File_app_dns_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NameServer); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dns_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NameServer_PriorityDomain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dns_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NameServer_OriginalRule); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dns_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config_HostMapping); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_app_dns_config_proto_msgTypes[0].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_app_dns_config_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 6,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,6 @@ option go_package = "github.com/xtls/xray-core/app/dns";
|
||||
option java_package = "com.xray.app.dns";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/address.proto";
|
||||
import "common/net/destination.proto";
|
||||
import "app/router/config.proto";
|
||||
|
||||
@@ -26,9 +25,19 @@ message NameServer {
|
||||
}
|
||||
|
||||
repeated PriorityDomain prioritized_domain = 2;
|
||||
repeated xray.app.router.GeoIP geoip = 3;
|
||||
repeated xray.app.router.GeoIP expected_geoip = 3;
|
||||
repeated OriginalRule original_rules = 4;
|
||||
QueryStrategy query_strategy = 7;
|
||||
bool actPrior = 8;
|
||||
string tag = 9;
|
||||
uint64 timeoutMs = 10;
|
||||
optional bool disableCache = 11;
|
||||
optional bool serveStale = 15;
|
||||
optional uint32 serveExpiredTTL = 16;
|
||||
bool finalQuery = 12;
|
||||
repeated xray.app.router.GeoIP unexpected_geoip = 13;
|
||||
bool actUnprior = 14;
|
||||
uint32 policyID = 17;
|
||||
}
|
||||
|
||||
enum DomainMatchingType {
|
||||
@@ -42,21 +51,14 @@ enum QueryStrategy {
|
||||
USE_IP = 0;
|
||||
USE_IP4 = 1;
|
||||
USE_IP6 = 2;
|
||||
USE_SYS = 3;
|
||||
}
|
||||
|
||||
message Config {
|
||||
// Nameservers used by this DNS. Only traditional UDP servers are support at
|
||||
// the moment. A special value 'localhost' as a domain address can be set to
|
||||
// use DNS on local system.
|
||||
repeated xray.common.net.Endpoint NameServers = 1 [deprecated = true];
|
||||
|
||||
// NameServer list used by this DNS client.
|
||||
// A special value 'localhost' as a domain address can be set to use DNS on local system.
|
||||
repeated NameServer name_server = 5;
|
||||
|
||||
// Static hosts. Domain to IP.
|
||||
// Deprecated. Use static_hosts.
|
||||
map<string, xray.common.net.IPOrDomain> Hosts = 2 [deprecated = true];
|
||||
|
||||
// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes
|
||||
// (IPv6).
|
||||
bytes client_ip = 3;
|
||||
@@ -81,9 +83,13 @@ message Config {
|
||||
|
||||
// DisableCache disables DNS cache
|
||||
bool disableCache = 8;
|
||||
bool serveStale = 12;
|
||||
uint32 serveExpiredTTL = 13;
|
||||
|
||||
QueryStrategy query_strategy = 9;
|
||||
|
||||
bool disableFallback = 10;
|
||||
bool disableFallbackIfMatch = 11;
|
||||
|
||||
bool enableParallelQuery = 14;
|
||||
}
|
||||
|
||||
598
app/dns/dns.go
598
app/dns/dns.go
@@ -1,37 +1,41 @@
|
||||
// Package dns is an implementation of core.DNS feature.
|
||||
package dns
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
go_errors "errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
router "github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DNS is a DNS rely server.
|
||||
type DNS struct {
|
||||
sync.Mutex
|
||||
tag string
|
||||
disableCache bool
|
||||
disableFallback bool
|
||||
disableFallbackIfMatch bool
|
||||
enableParallelQuery bool
|
||||
ipOption *dns.IPOption
|
||||
hosts *StaticHosts
|
||||
clients []*Client
|
||||
ctx context.Context
|
||||
domainMatcher strmatcher.IndexMatcher
|
||||
matcherInfos []*DomainMatcherInfo
|
||||
checkSystem bool
|
||||
}
|
||||
|
||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||
@@ -42,77 +46,94 @@ type DomainMatcherInfo struct {
|
||||
|
||||
// New creates a new DNS server with given configuration.
|
||||
func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
var tag string
|
||||
if len(config.Tag) > 0 {
|
||||
tag = config.Tag
|
||||
} else {
|
||||
tag = generateRandomTag()
|
||||
}
|
||||
|
||||
var clientIP net.IP
|
||||
switch len(config.ClientIp) {
|
||||
case 0, net.IPv4len, net.IPv6len:
|
||||
clientIP = net.IP(config.ClientIp)
|
||||
default:
|
||||
return nil, newError("unexpected client IP length ", len(config.ClientIp))
|
||||
return nil, errors.New("unexpected client IP length ", len(config.ClientIp))
|
||||
}
|
||||
|
||||
var ipOption *dns.IPOption
|
||||
var ipOption dns.IPOption
|
||||
checkSystem := false
|
||||
switch config.QueryStrategy {
|
||||
case QueryStrategy_USE_IP:
|
||||
ipOption = &dns.IPOption{
|
||||
ipOption = dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
}
|
||||
case QueryStrategy_USE_SYS:
|
||||
ipOption = dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
}
|
||||
checkSystem = true
|
||||
case QueryStrategy_USE_IP4:
|
||||
ipOption = &dns.IPOption{
|
||||
ipOption = dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
FakeEnable: false,
|
||||
}
|
||||
case QueryStrategy_USE_IP6:
|
||||
ipOption = &dns.IPOption{
|
||||
ipOption = dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
||||
}
|
||||
|
||||
hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts)
|
||||
hosts, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create hosts").Base(err)
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
|
||||
clients := []*Client{}
|
||||
var clients []*Client
|
||||
domainRuleCount := 0
|
||||
|
||||
var defaultTag = config.Tag
|
||||
if len(config.Tag) == 0 {
|
||||
defaultTag = generateRandomTag()
|
||||
}
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
err := parseDomains(ns)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns domain rules: ").Base(err)
|
||||
}
|
||||
|
||||
expectedGeoip, err := router.GetGeoIPList(ns.ExpectedGeoip)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns expectIPs rules: ").Base(err)
|
||||
}
|
||||
ns.ExpectedGeoip = expectedGeoip
|
||||
|
||||
unexpectedGeoip, err := router.GetGeoIPList(ns.UnexpectedGeoip)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns unexpectedGeoip rules: ").Base(err)
|
||||
}
|
||||
ns.UnexpectedGeoip = unexpectedGeoip
|
||||
|
||||
}
|
||||
domainRuleCount += len(ns.PrioritizedDomain)
|
||||
}
|
||||
|
||||
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
||||
domainMatcher := &strmatcher.MatcherGroup{}
|
||||
geoipContainer := router.GeoIPMatcherContainer{}
|
||||
|
||||
for _, endpoint := range config.NameServers {
|
||||
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
||||
client, err := NewSimpleClient(ctx, endpoint, clientIP)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create client").Base(err)
|
||||
}
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
clientIdx := len(clients)
|
||||
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) error {
|
||||
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
|
||||
midx := domainMatcher.Add(domainRule)
|
||||
matcherInfos[midx] = &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRuleIdx: uint16(originalRuleIdx),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
myClientIP := clientIP
|
||||
@@ -120,29 +141,54 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
myClientIP = net.IP(ns.ClientIp)
|
||||
}
|
||||
client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
|
||||
|
||||
disableCache := config.DisableCache
|
||||
if ns.DisableCache != nil {
|
||||
disableCache = *ns.DisableCache
|
||||
}
|
||||
|
||||
serveStale := config.ServeStale
|
||||
if ns.ServeStale != nil {
|
||||
serveStale = *ns.ServeStale
|
||||
}
|
||||
|
||||
serveExpiredTTL := config.ServeExpiredTTL
|
||||
if ns.ServeExpiredTTL != nil {
|
||||
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||
}
|
||||
|
||||
var tag = defaultTag
|
||||
if len(ns.Tag) > 0 {
|
||||
tag = ns.Tag
|
||||
}
|
||||
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
|
||||
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
|
||||
return nil, errors.New("no QueryStrategy available for ", ns.Address)
|
||||
}
|
||||
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create client").Base(err)
|
||||
return nil, errors.New("failed to create client").Base(err)
|
||||
}
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
// If there is no DNS client in config, add a `localhost` DNS client
|
||||
if len(clients) == 0 {
|
||||
clients = append(clients, NewLocalDNSClient())
|
||||
clients = append(clients, NewLocalDNSClient(ipOption))
|
||||
}
|
||||
|
||||
return &DNS{
|
||||
tag: tag,
|
||||
hosts: hosts,
|
||||
ipOption: ipOption,
|
||||
ipOption: &ipOption,
|
||||
clients: clients,
|
||||
ctx: ctx,
|
||||
domainMatcher: domainMatcher,
|
||||
matcherInfos: matcherInfos,
|
||||
disableCache: config.DisableCache,
|
||||
disableFallback: config.DisableFallback,
|
||||
disableFallbackIfMatch: config.DisableFallbackIfMatch,
|
||||
enableParallelQuery: config.EnableParallelQuery,
|
||||
checkSystem: checkSystem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -164,94 +210,67 @@ func (s *DNS) Close() error {
|
||||
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
||||
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
return inbound != nil && inbound.Tag == s.tag
|
||||
if inbound == nil {
|
||||
return false
|
||||
}
|
||||
for _, client := range s.clients {
|
||||
if client.tag == inbound.Tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupIP implements dns.Client.
|
||||
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
||||
if domain == "" {
|
||||
return nil, newError("empty domain name")
|
||||
}
|
||||
|
||||
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
||||
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
||||
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
|
||||
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
// Normalize the FQDN form query
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
if domain == "" {
|
||||
return nil, 0, errors.New("empty domain name")
|
||||
}
|
||||
|
||||
if s.checkSystem {
|
||||
supportIPv4, supportIPv6 := checkRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
||||
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
||||
}
|
||||
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
|
||||
// Static host lookup
|
||||
switch addrs := s.hosts.Lookup(domain, option); {
|
||||
switch addrs, err := s.hosts.Lookup(domain, option); {
|
||||
case err != nil:
|
||||
if go_errors.Is(err, dns.ErrEmptyResponse) {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, 0, errors.New("returning nil for domain ", domain).Base(err)
|
||||
case addrs == nil: // Domain not recorded in static host
|
||||
break
|
||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||
return nil, dns.ErrEmptyResponse
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
||||
newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
|
||||
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
|
||||
domain = addrs[0].Domain()
|
||||
default: // Successfully found ip records in static host
|
||||
newError("returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs).WriteToLog()
|
||||
return toNetIP(addrs)
|
||||
errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
|
||||
ips, err := toNetIP(addrs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return ips, 10, nil // Hosts ttl is 10
|
||||
}
|
||||
|
||||
// Name servers lookup
|
||||
errs := []error{}
|
||||
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||
for _, client := range s.sortClients(domain) {
|
||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||
newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog()
|
||||
continue
|
||||
}
|
||||
ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
|
||||
if len(ips) > 0 {
|
||||
return ips, nil
|
||||
}
|
||||
if err != nil {
|
||||
newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog()
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
|
||||
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
|
||||
return nil, err
|
||||
}
|
||||
if s.enableParallelQuery {
|
||||
return s.parallelQuery(domain, option)
|
||||
} else {
|
||||
return s.serialQuery(domain, option)
|
||||
}
|
||||
|
||||
return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||
}
|
||||
|
||||
// LookupHosts implements dns.HostsLookup.
|
||||
func (s *DNS) LookupHosts(domain string) *net.Address {
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
if domain == "" {
|
||||
return nil
|
||||
}
|
||||
// Normalize the FQDN form query
|
||||
addrs := s.hosts.Lookup(domain, *s.ipOption)
|
||||
if len(addrs) > 0 {
|
||||
newError("domain replaced: ", domain, " -> ", addrs[0].String()).AtInfo().WriteToLog()
|
||||
return &addrs[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIPOption implements ClientWithIPOption.
|
||||
func (s *DNS) GetIPOption() *dns.IPOption {
|
||||
return s.ipOption
|
||||
}
|
||||
|
||||
// SetQueryOption implements ClientWithIPOption.
|
||||
func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
|
||||
s.ipOption.IPv4Enable = isIPv4Enable
|
||||
s.ipOption.IPv6Enable = isIPv6Enable
|
||||
}
|
||||
|
||||
// SetFakeDNSOption implements ClientWithIPOption.
|
||||
func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
|
||||
s.ipOption.FakeEnable = isFakeEnable
|
||||
}
|
||||
|
||||
func (s *DNS) sortClients(domain string) []*Client {
|
||||
@@ -262,7 +281,11 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
|
||||
// Priority domain matching
|
||||
hasMatch := false
|
||||
for _, match := range s.domainMatcher.Match(domain) {
|
||||
MatchSlice := s.domainMatcher.Match(domain)
|
||||
sort.Slice(MatchSlice, func(i, j int) bool {
|
||||
return MatchSlice[i] < MatchSlice[j]
|
||||
})
|
||||
for _, match := range MatchSlice {
|
||||
info := s.matcherInfos[match]
|
||||
client := s.clients[info.clientIdx]
|
||||
domainRule := client.domains[info.domainRuleIdx]
|
||||
@@ -274,6 +297,9 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
hasMatch = true
|
||||
if client.finalQuery {
|
||||
return clients
|
||||
}
|
||||
}
|
||||
|
||||
if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {
|
||||
@@ -285,27 +311,367 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
clientUsed[idx] = true
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
if client.finalQuery {
|
||||
return clients
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(domainRules) > 0 {
|
||||
newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||
}
|
||||
if len(clientNames) > 0 {
|
||||
newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||
}
|
||||
|
||||
if len(clients) == 0 {
|
||||
clients = append(clients, s.clients[0])
|
||||
clientNames = append(clientNames, s.clients[0].Name())
|
||||
newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog()
|
||||
if len(s.clients) > 0 {
|
||||
clients = append(clients, s.clients[0])
|
||||
clientNames = append(clientNames, s.clients[0].Name())
|
||||
errors.LogWarning(s.ctx, "domain ", domain, " will use the first DNS: ", clientNames)
|
||||
} else {
|
||||
errors.LogError(s.ctx, "no DNS clients available for domain ", domain, " and no default clients configured")
|
||||
}
|
||||
}
|
||||
|
||||
return clients
|
||||
}
|
||||
|
||||
func mergeQueryErrors(domain string, errs []error) error {
|
||||
if len(errs) == 0 {
|
||||
return dns.ErrEmptyResponse
|
||||
}
|
||||
|
||||
var noRNF error
|
||||
for _, err := range errs {
|
||||
if go_errors.Is(err, errRecordNotFound) {
|
||||
continue // server no response, ignore
|
||||
} else if noRNF == nil {
|
||||
noRNF = err
|
||||
} else if !go_errors.Is(err, noRNF) {
|
||||
return errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||
}
|
||||
}
|
||||
if go_errors.Is(noRNF, dns.ErrEmptyResponse) {
|
||||
return dns.ErrEmptyResponse
|
||||
}
|
||||
if noRNF == nil {
|
||||
noRNF = errRecordNotFound
|
||||
}
|
||||
return errors.New("returning nil for domain ", domain).Base(noRNF)
|
||||
}
|
||||
|
||||
func (s *DNS) serialQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
var errs []error
|
||||
for _, client := range s.sortClients(domain) {
|
||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
|
||||
|
||||
if len(ips) > 0 {
|
||||
return ips, ttl, nil
|
||||
}
|
||||
|
||||
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name(), " in serial query mode")
|
||||
if err == nil {
|
||||
err = dns.ErrEmptyResponse
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil, 0, mergeQueryErrors(domain, errs)
|
||||
}
|
||||
|
||||
func (s *DNS) parallelQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
var errs []error
|
||||
clients := s.sortClients(domain)
|
||||
|
||||
resultsChan := asyncQueryAll(domain, option, clients, s.ctx)
|
||||
|
||||
groups, groupOf := makeGroups( /*s.ctx,*/ clients)
|
||||
results := make([]*queryResult, len(clients))
|
||||
pending := make([]int, len(groups))
|
||||
for gi, g := range groups {
|
||||
pending[gi] = g.end - g.start + 1
|
||||
}
|
||||
|
||||
nextGroup := 0
|
||||
for range clients {
|
||||
result := <-resultsChan
|
||||
results[result.index] = &result
|
||||
|
||||
gi := groupOf[result.index]
|
||||
pending[gi]--
|
||||
|
||||
for nextGroup < len(groups) {
|
||||
g := groups[nextGroup]
|
||||
|
||||
// group race, minimum rtt -> return
|
||||
for j := g.start; j <= g.end; j++ {
|
||||
r := results[j]
|
||||
if r != nil && r.err == nil && len(r.ips) > 0 {
|
||||
return r.ips, r.ttl, nil
|
||||
}
|
||||
}
|
||||
|
||||
// current group is incomplete and no one success -> continue pending
|
||||
if pending[nextGroup] > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// all failed -> log and continue next group
|
||||
for j := g.start; j <= g.end; j++ {
|
||||
r := results[j]
|
||||
e := r.err
|
||||
if e == nil {
|
||||
e = dns.ErrEmptyResponse
|
||||
}
|
||||
errors.LogInfoInner(s.ctx, e, "failed to lookup ip for domain ", domain, " at server ", clients[j].Name(), " in parallel query mode")
|
||||
errs = append(errs, e)
|
||||
}
|
||||
nextGroup++
|
||||
}
|
||||
}
|
||||
|
||||
return nil, 0, mergeQueryErrors(domain, errs)
|
||||
}
|
||||
|
||||
type queryResult struct {
|
||||
ips []net.IP
|
||||
ttl uint32
|
||||
err error
|
||||
index int
|
||||
}
|
||||
|
||||
func asyncQueryAll(domain string, option dns.IPOption, clients []*Client, ctx context.Context) chan queryResult {
|
||||
if len(clients) == 0 {
|
||||
ch := make(chan queryResult)
|
||||
close(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
ch := make(chan queryResult, len(clients))
|
||||
for i, client := range clients {
|
||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||
errors.LogDebug(ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||
ch <- queryResult{err: dns.ErrEmptyResponse, index: i}
|
||||
continue
|
||||
}
|
||||
|
||||
go func(i int, c *Client) {
|
||||
qctx := ctx
|
||||
if !c.server.IsDisableCache() {
|
||||
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), c.timeoutMs*2)
|
||||
qctx = nctx
|
||||
defer cancel()
|
||||
}
|
||||
ips, ttl, err := c.QueryIP(qctx, domain, option)
|
||||
ch <- queryResult{ips: ips, ttl: ttl, err: err, index: i}
|
||||
}(i, client)
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
type group struct{ start, end int }
|
||||
|
||||
// merge only adjacent and rule-equivalent Client into a single group
|
||||
func makeGroups( /*ctx context.Context,*/ clients []*Client) ([]group, []int) {
|
||||
n := len(clients)
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
groups := make([]group, 0, n)
|
||||
groupOf := make([]int, n)
|
||||
|
||||
s, e := 0, 0
|
||||
for i := 1; i < n; i++ {
|
||||
if clients[i-1].policyID == clients[i].policyID {
|
||||
e = i
|
||||
} else {
|
||||
for k := s; k <= e; k++ {
|
||||
groupOf[k] = len(groups)
|
||||
}
|
||||
groups = append(groups, group{start: s, end: e})
|
||||
s, e = i, i
|
||||
}
|
||||
}
|
||||
for k := s; k <= e; k++ {
|
||||
groupOf[k] = len(groups)
|
||||
}
|
||||
groups = append(groups, group{start: s, end: e})
|
||||
|
||||
// var b strings.Builder
|
||||
// b.WriteString("dns grouping: total clients=")
|
||||
// b.WriteString(strconv.Itoa(n))
|
||||
// b.WriteString(", groups=")
|
||||
// b.WriteString(strconv.Itoa(len(groups)))
|
||||
|
||||
// for gi, g := range groups {
|
||||
// b.WriteString("\n [")
|
||||
// b.WriteString(strconv.Itoa(g.start))
|
||||
// b.WriteString("..")
|
||||
// b.WriteString(strconv.Itoa(g.end))
|
||||
// b.WriteString("] gid=")
|
||||
// b.WriteString(strconv.Itoa(gi))
|
||||
// b.WriteString(" pid=")
|
||||
// b.WriteString(strconv.FormatUint(uint64(clients[g.start].policyID), 10))
|
||||
// b.WriteString(" members: ")
|
||||
|
||||
// for i := g.start; i <= g.end; i++ {
|
||||
// if i > g.start {
|
||||
// b.WriteString(", ")
|
||||
// }
|
||||
// b.WriteString(strconv.Itoa(i))
|
||||
// b.WriteByte(':')
|
||||
// b.WriteString(clients[i].Name())
|
||||
// }
|
||||
// }
|
||||
// errors.LogDebug(ctx, b.String())
|
||||
|
||||
return groups, groupOf
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return New(ctx, config.(*Config))
|
||||
}))
|
||||
}
|
||||
|
||||
func probeRoutes() (ipv4 bool, ipv6 bool) {
|
||||
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
|
||||
ipv4 = true
|
||||
conn.Close()
|
||||
}
|
||||
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
|
||||
ipv6 = true
|
||||
conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var routeCache struct {
|
||||
sync.Once
|
||||
sync.RWMutex
|
||||
expire time.Time
|
||||
ipv4, ipv6 bool
|
||||
}
|
||||
|
||||
func checkRoutes() (bool, bool) {
|
||||
if !isGUIPlatform {
|
||||
routeCache.Once.Do(func() {
|
||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
|
||||
})
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
|
||||
routeCache.RWMutex.RLock()
|
||||
now := time.Now()
|
||||
if routeCache.expire.After(now) {
|
||||
routeCache.RWMutex.RUnlock()
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
routeCache.RWMutex.RUnlock()
|
||||
|
||||
routeCache.RWMutex.Lock()
|
||||
defer routeCache.RWMutex.Unlock()
|
||||
|
||||
now = time.Now()
|
||||
if routeCache.expire.After(now) { // double-check
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
|
||||
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
|
||||
var isGUIPlatform = detectGUIPlatform()
|
||||
|
||||
func detectGUIPlatform() bool {
|
||||
switch runtime.GOOS {
|
||||
case "android", "ios", "windows", "darwin":
|
||||
return true
|
||||
case "linux", "freebsd", "openbsd":
|
||||
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
|
||||
return true
|
||||
}
|
||||
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseDomains(ns *NameServer) error {
|
||||
pureDomains := []*router.Domain{}
|
||||
|
||||
// convert to pure domain
|
||||
for _, pd := range ns.PrioritizedDomain {
|
||||
pureDomains = append(pureDomains, &router.Domain{
|
||||
Type: router.Domain_Type(pd.Type),
|
||||
Value: pd.Domain,
|
||||
})
|
||||
}
|
||||
|
||||
domainList := []*router.Domain{}
|
||||
for _, domain := range pureDomains {
|
||||
val := strings.Split(domain.Value, "_")
|
||||
if len(val) >= 2 {
|
||||
|
||||
fileName := val[0]
|
||||
code := val[1]
|
||||
|
||||
bs, err := filesystem.ReadAsset(fileName)
|
||||
if err != nil {
|
||||
return errors.New("failed to load file: ", fileName).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(code))
|
||||
var geosite router.GeoSite
|
||||
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return errors.New("failed Unmarshal :").Base(err)
|
||||
}
|
||||
|
||||
// parse attr
|
||||
if len(val) == 3 {
|
||||
siteWithAttr := strings.Split(val[2], ",")
|
||||
attrs := router.ParseAttrs(siteWithAttr)
|
||||
if !attrs.IsEmpty() {
|
||||
filteredDomains := make([]*router.Domain, 0, len(pureDomains))
|
||||
for _, domain := range geosite.Domain {
|
||||
if attrs.Match(domain) {
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
geosite.Domain = filteredDomains
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
domainList = append(domainList, geosite.Domain...)
|
||||
|
||||
// update ns.OriginalRules Size
|
||||
ruleTag := strings.Join(val, ":")
|
||||
for i, oRule := range ns.OriginalRules {
|
||||
if oRule.Rule == strings.ToLower(ruleTag) {
|
||||
ns.OriginalRules[i].Size = uint32(len(geosite.Domain))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
domainList = append(domainList, domain)
|
||||
}
|
||||
}
|
||||
|
||||
// convert back to NameServer_PriorityDomain
|
||||
ns.PrioritizedDomain = []*NameServer_PriorityDomain{}
|
||||
for _, pd := range domainList {
|
||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, &NameServer_PriorityDomain{
|
||||
Type: ToDomainMatchingType(pd.Type),
|
||||
Domain: pd.Value,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA:
|
||||
ans.MsgHdr.Rcode = dns.RcodeNameError
|
||||
|
||||
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeA:
|
||||
ans.MsgHdr.Rcode = dns.RcodeNameError
|
||||
|
||||
case q.Name == "hostname." && q.Qtype == dns.TypeA:
|
||||
rr, _ := dns.NewRR("hostname. IN A 127.0.0.1")
|
||||
ans.Answer = append(ans.Answer, rr)
|
||||
@@ -117,22 +120,23 @@ func TestUDPServerSubnet(t *testing.T) {
|
||||
Handler: &staticHandler{},
|
||||
UDPSize: 1200,
|
||||
}
|
||||
|
||||
go dnsServer.ListenAndServe()
|
||||
time.Sleep(time.Second)
|
||||
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
},
|
||||
ClientIp: []byte{7, 8, 9, 10},
|
||||
@@ -153,7 +157,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
||||
|
||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -183,15 +187,17 @@ func TestUDPServer(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -212,7 +218,7 @@ func TestUDPServer(t *testing.T) {
|
||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -227,7 +233,7 @@ func TestUDPServer(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("facebook.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -242,7 +248,7 @@ func TestUDPServer(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
|
||||
_, _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -256,7 +262,7 @@ func TestUDPServer(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -272,7 +278,7 @@ func TestUDPServer(t *testing.T) {
|
||||
dnsServer.Shutdown()
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -303,18 +309,18 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
@@ -353,7 +359,7 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -389,15 +395,17 @@ func TestUDPServerIPv6(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -417,7 +425,7 @@ func TestUDPServerIPv6(t *testing.T) {
|
||||
|
||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||
{
|
||||
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -448,15 +456,17 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
@@ -484,7 +494,7 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("example.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("example.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -529,9 +539,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
CountryCode: "local",
|
||||
// local
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
// inner ip, will not match
|
||||
@@ -553,9 +563,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
CountryCode: "test",
|
||||
// test
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
@@ -564,7 +574,7 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "test",
|
||||
// test
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 4},
|
||||
@@ -595,7 +605,7 @@ func TestIPMatch(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
|
||||
{
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -631,18 +641,18 @@ func TestLocalDomain(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
@@ -657,9 +667,9 @@ func TestLocalDomain(t *testing.T) {
|
||||
// Equivalent of dotless:localhost
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match localhost, localhost-a and localhost-b,
|
||||
CountryCode: "local",
|
||||
// local
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||
@@ -718,7 +728,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
|
||||
{ // Will match dotless:
|
||||
ips, err := client.LookupIP("hostname", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("hostname", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -733,7 +743,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match domain:local
|
||||
ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("hostname.local", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -748,7 +758,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match static ip
|
||||
ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -763,7 +773,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match domain replacing
|
||||
ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -777,8 +787,8 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
||||
ips, err := client.LookupIP("localhost", feature_dns.IPOption{
|
||||
{ // Will match dotless:localhost, but not expectedIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
||||
ips, _, err := client.LookupIP("localhost", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -792,8 +802,8 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||
ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{
|
||||
{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
|
||||
ips, _, err := client.LookupIP("localhost-a", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -807,8 +817,8 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||
ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{
|
||||
{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
|
||||
ips, _, err := client.LookupIP("localhost-b", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -823,7 +833,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match dotless:
|
||||
ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -859,18 +869,18 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&Config{
|
||||
NameServers: []*net.Endpoint{
|
||||
{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
NameServer: []*NameServer{
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 9999, /* unreachable */
|
||||
},
|
||||
},
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
@@ -887,7 +897,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
Domain: "google.com",
|
||||
},
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.8.8 and 8.8.4.4
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
@@ -912,7 +922,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
Domain: "google.com",
|
||||
},
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
|
||||
@@ -936,7 +946,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
Domain: "api.google.com",
|
||||
},
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.7 (api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
||||
@@ -960,7 +970,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
Domain: "v2.api.google.com",
|
||||
},
|
||||
},
|
||||
Geoip: []*router.GeoIP{
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.8 (v2.api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
|
||||
@@ -989,7 +999,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
|
||||
{ // Will match server 1,2 and server 1 returns expected ip
|
||||
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -1004,7 +1014,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
|
||||
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
FakeEnable: false,
|
||||
@@ -1019,7 +1029,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match server 3,1,2 and server 3 returns expected one
|
||||
ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("api.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
@@ -1034,7 +1044,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
{ // Will match server 4,3,1,2 and server 4 returns expected one
|
||||
ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
|
||||
ips, _, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
|
||||
@@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,10 +14,12 @@ import (
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
)
|
||||
|
||||
// Fqdn normalizes domain make sure it ends with '.'
|
||||
// case-sensitive
|
||||
func Fqdn(domain string) string {
|
||||
if len(domain) > 0 && strings.HasSuffix(domain, ".") {
|
||||
return domain
|
||||
@@ -31,30 +34,29 @@ type record struct {
|
||||
|
||||
// IPRecord is a cacheable item for a resolved domain
|
||||
type IPRecord struct {
|
||||
ReqID uint16
|
||||
IP []net.Address
|
||||
Expire time.Time
|
||||
RCode dnsmessage.RCode
|
||||
ReqID uint16
|
||||
IP []net.IP
|
||||
Expire time.Time
|
||||
RCode dnsmessage.RCode
|
||||
RawHeader *dnsmessage.Header
|
||||
}
|
||||
|
||||
func (r *IPRecord) getIPs() ([]net.Address, error) {
|
||||
if r == nil || r.Expire.Before(time.Now()) {
|
||||
return nil, errRecordNotFound
|
||||
func (r *IPRecord) getIPs() ([]net.IP, int32, error) {
|
||||
if r == nil {
|
||||
return nil, 0, errRecordNotFound
|
||||
}
|
||||
|
||||
untilExpire := time.Until(r.Expire).Seconds()
|
||||
ttl := int32(math.Ceil(untilExpire))
|
||||
|
||||
if r.RCode != dnsmessage.RCodeSuccess {
|
||||
return nil, dns_feature.RCodeError(r.RCode)
|
||||
return nil, ttl, dns_feature.RCodeError(r.RCode)
|
||||
}
|
||||
if len(r.IP) == 0 {
|
||||
return nil, ttl, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
return r.IP, nil
|
||||
}
|
||||
|
||||
func isNewer(baseRec *IPRecord, newRec *IPRecord) bool {
|
||||
if newRec == nil {
|
||||
return false
|
||||
}
|
||||
if baseRec == nil {
|
||||
return true
|
||||
}
|
||||
return baseRec.Expire.Before(newRec.Expire)
|
||||
return r.IP, ttl, nil
|
||||
}
|
||||
|
||||
var errRecordNotFound = errors.New("record not found")
|
||||
@@ -67,49 +69,59 @@ type dnsRequest struct {
|
||||
msg *dnsmessage.Message
|
||||
}
|
||||
|
||||
func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource {
|
||||
if len(clientIP) == 0 {
|
||||
func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
||||
if len(clientIP) == 0 && padding == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var netmask int
|
||||
var family uint16
|
||||
|
||||
if len(clientIP) == 4 {
|
||||
family = 1
|
||||
netmask = 24 // 24 for IPV4, 96 for IPv6
|
||||
} else {
|
||||
family = 2
|
||||
netmask = 96
|
||||
}
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(b[0:], family)
|
||||
b[2] = byte(netmask)
|
||||
b[3] = 0
|
||||
switch family {
|
||||
case 1:
|
||||
ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))
|
||||
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
case 2:
|
||||
ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))
|
||||
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
}
|
||||
|
||||
const EDNS0SUBNET = 0x08
|
||||
const EDNS0SUBNET = 0x8
|
||||
const EDNS0PADDING = 0xc
|
||||
|
||||
opt := new(dnsmessage.Resource)
|
||||
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
||||
body := dnsmessage.OPTResource{}
|
||||
opt.Body = &body
|
||||
|
||||
opt.Body = &dnsmessage.OPTResource{
|
||||
Options: []dnsmessage.Option{
|
||||
{
|
||||
if len(clientIP) != 0 {
|
||||
var netmask int
|
||||
var family uint16
|
||||
|
||||
if len(clientIP) == 4 {
|
||||
family = 1
|
||||
netmask = 24 // 24 for IPV4, 96 for IPv6
|
||||
} else {
|
||||
family = 2
|
||||
netmask = 96
|
||||
}
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(b[0:], family)
|
||||
b[2] = byte(netmask)
|
||||
b[3] = 0
|
||||
switch family {
|
||||
case 1:
|
||||
ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))
|
||||
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
case 2:
|
||||
ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))
|
||||
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||
b = append(b, ip[:needLength]...)
|
||||
}
|
||||
|
||||
body.Options = append(body.Options,
|
||||
dnsmessage.Option{
|
||||
Code: EDNS0SUBNET,
|
||||
Data: b,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if padding != 0 {
|
||||
body.Options = append(body.Options,
|
||||
dnsmessage.Option{
|
||||
Code: EDNS0PADDING,
|
||||
Data: make([]byte, padding),
|
||||
})
|
||||
}
|
||||
|
||||
return opt
|
||||
@@ -171,17 +183,18 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
||||
var parser dnsmessage.Parser
|
||||
h, err := parser.Start(payload)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse DNS response").Base(err).AtWarning()
|
||||
return nil, errors.New("failed to parse DNS response").Base(err).AtWarning()
|
||||
}
|
||||
if err := parser.SkipAllQuestions(); err != nil {
|
||||
return nil, newError("failed to skip questions in DNS response").Base(err).AtWarning()
|
||||
return nil, errors.New("failed to skip questions in DNS response").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
ipRecord := &IPRecord{
|
||||
ReqID: h.ID,
|
||||
RCode: h.RCode,
|
||||
Expire: now.Add(time.Second * 600),
|
||||
ReqID: h.ID,
|
||||
RCode: h.RCode,
|
||||
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
||||
RawHeader: &h,
|
||||
}
|
||||
|
||||
L:
|
||||
@@ -189,14 +202,14 @@ L:
|
||||
ah, err := parser.AnswerHeader()
|
||||
if err != nil {
|
||||
if err != dnsmessage.ErrSectionDone {
|
||||
newError("failed to parse answer section for domain: ", ah.Name.String()).Base(err).WriteToLog()
|
||||
errors.LogInfoInner(context.Background(), err, "failed to parse answer section for domain: ", ah.Name.String())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
ttl := ah.TTL
|
||||
if ttl == 0 {
|
||||
ttl = 600
|
||||
ttl = 1
|
||||
}
|
||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||
if ipRecord.Expire.After(expire) {
|
||||
@@ -207,20 +220,23 @@ L:
|
||||
case dnsmessage.TypeA:
|
||||
ans, err := parser.AResource()
|
||||
if err != nil {
|
||||
newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog()
|
||||
errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name)
|
||||
break L
|
||||
}
|
||||
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]))
|
||||
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]).IP())
|
||||
case dnsmessage.TypeAAAA:
|
||||
ans, err := parser.AAAAResource()
|
||||
if err != nil {
|
||||
newError("failed to parse AAAA record for domain: ", ah.Name).Base(err).WriteToLog()
|
||||
errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name)
|
||||
break L
|
||||
}
|
||||
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:]))
|
||||
newIP := net.IPAddress(ans.AAAA[:]).IP()
|
||||
if len(newIP) == net.IPv6len {
|
||||
ipRecord.IP = append(ipRecord.IP, newIP)
|
||||
}
|
||||
default:
|
||||
if err := parser.SkipAnswer(); err != nil {
|
||||
newError("failed to skip answer").Base(err).WriteToLog()
|
||||
errors.LogInfoInner(context.Background(), err, "failed to skip answer")
|
||||
break L
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -18,31 +18,31 @@ func Test_parseResponse(t *testing.T) {
|
||||
|
||||
ans := new(dns.Msg)
|
||||
ans.Id = 0
|
||||
p = append(p, common.Must2(ans.Pack()).([]byte))
|
||||
p = append(p, common.Must2(ans.Pack()))
|
||||
|
||||
p = append(p, []byte{})
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 1
|
||||
ans.Answer = append(ans.Answer,
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
|
||||
common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")),
|
||||
)
|
||||
p = append(p, common.Must2(ans.Pack()).([]byte))
|
||||
p = append(p, common.Must2(ans.Pack()))
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 2
|
||||
ans.Answer = append(ans.Answer,
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")).(dns.RR),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8888")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8844")),
|
||||
)
|
||||
p = append(p, common.Must2(ans.Pack()).([]byte))
|
||||
p = append(p, common.Must2(ans.Pack()))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -51,7 +51,7 @@ func Test_parseResponse(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess},
|
||||
&IPRecord{0, []net.IP(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||
false,
|
||||
},
|
||||
{
|
||||
@@ -63,15 +63,16 @@ func Test_parseResponse(t *testing.T) {
|
||||
"a record",
|
||||
&IPRecord{
|
||||
1,
|
||||
[]net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")},
|
||||
[]net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")},
|
||||
time.Time{},
|
||||
dnsmessage.RCodeSuccess,
|
||||
nil,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"aaaa record",
|
||||
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess},
|
||||
&IPRecord{2, []net.IP{net.ParseIP("2001:4860:4860::8888"), net.ParseIP("2001:4860:4860::8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||
false,
|
||||
},
|
||||
}
|
||||
@@ -84,11 +85,12 @@ func Test_parseResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
if got != nil {
|
||||
// reset the time
|
||||
// reset the time and RawHeader
|
||||
got.Expire = time.Time{}
|
||||
got.RawHeader = nil
|
||||
}
|
||||
if cmp.Diff(got, tt.want) != "" {
|
||||
t.Errorf(cmp.Diff(got, tt.want))
|
||||
t.Error(cmp.Diff(got, tt.want))
|
||||
// t.Errorf("handleResponse() = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
@@ -154,7 +156,7 @@ func Test_genEDNS0Options(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := genEDNS0Options(tt.args.clientIP); got == nil {
|
||||
if got := genEDNS0Options(tt.args.clientIP, 0); got == nil {
|
||||
t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package dns
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package fakedns
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -4,19 +4,19 @@ import (
|
||||
"context"
|
||||
"math"
|
||||
"math/big"
|
||||
gonet "net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/cache"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
type Holder struct {
|
||||
domainToIP cache.Lru
|
||||
ipRange *gonet.IPNet
|
||||
ipRange *net.IPNet
|
||||
mu *sync.Mutex
|
||||
|
||||
config *FakeDnsPool
|
||||
@@ -45,7 +45,7 @@ func (fkdns *Holder) Start() error {
|
||||
if fkdns.config != nil && fkdns.config.IpPool != "" && fkdns.config.LruSize != 0 {
|
||||
return fkdns.initializeFromConfig()
|
||||
}
|
||||
return newError("invalid fakeDNS setting")
|
||||
return errors.New("invalid fakeDNS setting")
|
||||
}
|
||||
|
||||
func (fkdns *Holder) Close() error {
|
||||
@@ -60,7 +60,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
||||
var err error
|
||||
|
||||
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
||||
return nil, newError("Unable to create Fake Dns Engine").Base(err).AtError()
|
||||
return nil, errors.New("Unable to create Fake Dns Engine").Base(err).AtError()
|
||||
}
|
||||
err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
|
||||
if err != nil {
|
||||
@@ -78,17 +78,17 @@ func (fkdns *Holder) initializeFromConfig() error {
|
||||
}
|
||||
|
||||
func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
||||
var ipRange *gonet.IPNet
|
||||
var ipRange *net.IPNet
|
||||
var err error
|
||||
|
||||
if _, ipRange, err = gonet.ParseCIDR(ipPoolCidr); err != nil {
|
||||
return newError("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError()
|
||||
if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
|
||||
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError()
|
||||
}
|
||||
|
||||
ones, bits := ipRange.Mask.Size()
|
||||
rooms := bits - ones
|
||||
if math.Log2(float64(lruSize)) >= float64(rooms) {
|
||||
return newError("LRU size is bigger than subnet size").AtError()
|
||||
return errors.New("LRU size is bigger than subnet size").AtError()
|
||||
}
|
||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||
fkdns.ipRange = ipRange
|
||||
@@ -137,7 +137,7 @@ func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {
|
||||
if k, ok := fkdns.domainToIP.GetKeyFromValue(ip); ok {
|
||||
return k.(string)
|
||||
}
|
||||
newError("A fake ip request to ", ip, ", however there is no matching domain name in fake DNS").AtInfo().WriteToLog()
|
||||
errors.LogInfo(context.Background(), "A fake ip request to ", ip, ", however there is no matching domain name in fake DNS")
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -192,10 +192,10 @@ func (h *HolderMulti) Start() error {
|
||||
for _, v := range h.holders {
|
||||
if v.config != nil && v.config.IpPool != "" && v.config.LruSize != 0 {
|
||||
if err := v.Start(); err != nil {
|
||||
return newError("Cannot start all fake dns pools").Base(err)
|
||||
return errors.New("Cannot start all fake dns pools").Base(err)
|
||||
}
|
||||
} else {
|
||||
return newError("invalid fakeDNS setting")
|
||||
return errors.New("invalid fakeDNS setting")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -204,7 +204,7 @@ func (h *HolderMulti) Start() error {
|
||||
func (h *HolderMulti) Close() error {
|
||||
for _, v := range h.holders {
|
||||
if err := v.Close(); err != nil {
|
||||
return newError("Cannot close all fake dns pools").Base(err)
|
||||
return errors.New("Cannot close all fake dns pools").Base(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
package fakedns
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/dns/fakedns/fakedns.proto
|
||||
|
||||
package fakedns
|
||||
@@ -31,11 +31,9 @@ type FakeDnsPool struct {
|
||||
|
||||
func (x *FakeDnsPool) Reset() {
|
||||
*x = FakeDnsPool{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FakeDnsPool) String() string {
|
||||
@@ -46,7 +44,7 @@ func (*FakeDnsPool) ProtoMessage() {}
|
||||
|
||||
func (x *FakeDnsPool) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -85,11 +83,9 @@ type FakeDnsPoolMulti struct {
|
||||
|
||||
func (x *FakeDnsPoolMulti) Reset() {
|
||||
*x = FakeDnsPoolMulti{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FakeDnsPoolMulti) String() string {
|
||||
@@ -100,7 +96,7 @@ func (*FakeDnsPoolMulti) ProtoMessage() {}
|
||||
|
||||
func (x *FakeDnsPoolMulti) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -159,7 +155,7 @@ func file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_app_dns_fakedns_fakedns_proto_goTypes = []interface{}{
|
||||
var file_app_dns_fakedns_fakedns_proto_goTypes = []any{
|
||||
(*FakeDnsPool)(nil), // 0: xray.app.dns.fakedns.FakeDnsPool
|
||||
(*FakeDnsPoolMulti)(nil), // 1: xray.app.dns.fakedns.FakeDnsPoolMulti
|
||||
}
|
||||
@@ -177,32 +173,6 @@ func file_app_dns_fakedns_fakedns_proto_init() {
|
||||
if File_app_dns_fakedns_fakedns_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_dns_fakedns_fakedns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FakeDnsPool); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_dns_fakedns_fakedns_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FakeDnsPoolMulti); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fakedns
|
||||
|
||||
import (
|
||||
gonet "net"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -155,7 +154,7 @@ func TestFakeDNSMulti(t *testing.T) {
|
||||
assert.True(t, inPool)
|
||||
})
|
||||
t.Run("ipv6", func(t *testing.T) {
|
||||
ip, err := gonet.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
|
||||
ip, err := net.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
|
||||
assert.Nil(t, err)
|
||||
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||
assert.True(t, inPool)
|
||||
@@ -165,7 +164,7 @@ func TestFakeDNSMulti(t *testing.T) {
|
||||
assert.False(t, inPool)
|
||||
})
|
||||
t.Run("ipv6_inverse", func(t *testing.T) {
|
||||
ip, err := gonet.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
|
||||
ip, err := net.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
|
||||
assert.Nil(t, err)
|
||||
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||
assert.False(t, inPool)
|
||||
|
||||
106
app/dns/hosts.go
106
app/dns/hosts.go
@@ -1,10 +1,12 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
@@ -15,50 +17,44 @@ type StaticHosts struct {
|
||||
}
|
||||
|
||||
// NewStaticHosts creates a new StaticHosts instance.
|
||||
func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
|
||||
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
g := new(strmatcher.MatcherGroup)
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, len(hosts)+len(legacy)+16),
|
||||
ips: make([][]net.Address, len(hosts)+16),
|
||||
matchers: g,
|
||||
}
|
||||
|
||||
if legacy != nil {
|
||||
features.PrintDeprecatedFeatureWarning("simple host mapping")
|
||||
|
||||
for domain, ip := range legacy {
|
||||
matcher, err := strmatcher.Full.New(domain)
|
||||
common.Must(err)
|
||||
id := g.Add(matcher)
|
||||
|
||||
address := ip.AsAddress()
|
||||
if address.Family().IsDomain() {
|
||||
return nil, newError("invalid domain address in static hosts: ", address.Domain()).AtWarning()
|
||||
}
|
||||
|
||||
sh.ips[id] = []net.Address{address}
|
||||
}
|
||||
}
|
||||
|
||||
for _, mapping := range hosts {
|
||||
matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create domain matcher").Base(err)
|
||||
errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
continue
|
||||
}
|
||||
id := g.Add(matcher)
|
||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||
switch {
|
||||
case len(mapping.ProxiedDomain) > 0:
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
if mapping.ProxiedDomain[0] == '#' {
|
||||
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, dns.RCodeError(rcode))
|
||||
} else {
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
}
|
||||
case len(mapping.Ip) > 0:
|
||||
for _, ip := range mapping.Ip {
|
||||
addr := net.IPAddress(ip)
|
||||
if addr == nil {
|
||||
return nil, newError("invalid IP address in static hosts: ", ip).AtWarning()
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
continue
|
||||
}
|
||||
ips = append(ips, addr)
|
||||
}
|
||||
default:
|
||||
return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sh.ips[id] = ips
|
||||
@@ -77,33 +73,51 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
||||
var ips []net.Address
|
||||
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
ips := make([]net.Address, 0)
|
||||
found := false
|
||||
for _, id := range h.matchers.Match(domain) {
|
||||
ips = append(ips, h.ips[id]...)
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
||||
switch addrs := h.lookupInternal(domain); {
|
||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
return nil
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||
newError("found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it").AtDebug().WriteToLog()
|
||||
if maxDepth > 0 {
|
||||
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||
if unwrapped != nil {
|
||||
return unwrapped
|
||||
for _, v := range h.ips[id] {
|
||||
if err, ok := v.(dns.RCodeError); ok {
|
||||
if uint16(err) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
ips = append(ips, h.ips[id]...)
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||
switch addrs, err := h.lookupInternal(domain); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
return addrs, nil
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||
if maxDepth > 0 {
|
||||
unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unwrapped != nil {
|
||||
return unwrapped, nil
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
default: // IP record found, return a non-nil IP array
|
||||
return filterIP(addrs, option)
|
||||
return filterIP(addrs, option), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address {
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||
return h.lookup(domain, option, 5)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ import (
|
||||
|
||||
func TestStaticHosts(t *testing.T) {
|
||||
pb := []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "lan",
|
||||
ProxiedDomain: "#3",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
@@ -50,11 +55,18 @@ func TestStaticHosts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
hosts, err := NewStaticHosts(pb, nil)
|
||||
hosts, err := NewStaticHosts(pb)
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("example.com", dns.IPOption{
|
||||
_, err := hosts.Lookup("example.com.lan", dns.IPOption{})
|
||||
if dns.RCodeFromError(err) != 3 {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("example.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
@@ -67,7 +79,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
domain := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||
domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
})
|
||||
@@ -80,7 +92,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||
domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
})
|
||||
@@ -93,7 +105,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||
ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
@@ -106,7 +118,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("baidu.com", dns.IPOption{
|
||||
ips, _ := hosts.Lookup("baidu.com", dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
@@ -19,23 +20,32 @@ import (
|
||||
type Server interface {
|
||||
// Name of the Client.
|
||||
Name() string
|
||||
|
||||
IsDisableCache() bool
|
||||
|
||||
// QueryIP sends IP queries to its configured server.
|
||||
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
||||
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)
|
||||
}
|
||||
|
||||
// Client is the interface for DNS client.
|
||||
type Client struct {
|
||||
server Server
|
||||
clientIP net.IP
|
||||
skipFallback bool
|
||||
domains []string
|
||||
expectIPs []*router.GeoIPMatcher
|
||||
server Server
|
||||
skipFallback bool
|
||||
domains []string
|
||||
expectedIPs router.GeoIPMatcher
|
||||
unexpectedIPs router.GeoIPMatcher
|
||||
actPrior bool
|
||||
actUnprior bool
|
||||
tag string
|
||||
timeoutMs time.Duration
|
||||
finalQuery bool
|
||||
ipOption *dns.IPOption
|
||||
checkSystem bool
|
||||
policyID uint32
|
||||
}
|
||||
|
||||
var errExpectedIPNonMatch = errors.New("expectIPs not match")
|
||||
|
||||
// NewServer creates a name server object according to the network destination url.
|
||||
func NewServer(dest net.Destination, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (Server, error) {
|
||||
func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (Server, error) {
|
||||
if address := dest.Address; address.Family().IsDomain() {
|
||||
u, err := url.Parse(address.Domain())
|
||||
if err != nil {
|
||||
@@ -44,27 +54,38 @@ func NewServer(dest net.Destination, dispatcher routing.Dispatcher, queryStrateg
|
||||
switch {
|
||||
case strings.EqualFold(u.String(), "localhost"):
|
||||
return NewLocalNameServer(), nil
|
||||
case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
|
||||
return NewDoHNameServer(u, dispatcher, queryStrategy)
|
||||
case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
|
||||
return NewDoHLocalNameServer(u, queryStrategy), nil
|
||||
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
|
||||
return NewDoHNameServer(u, dispatcher, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
|
||||
return NewDoHNameServer(u, dispatcher, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
|
||||
return NewDoHNameServer(u, nil, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||
case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
|
||||
return NewDoHNameServer(u, nil, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
||||
return NewQUICNameServer(u, queryStrategy)
|
||||
return NewQUICNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
|
||||
return NewTCPNameServer(u, dispatcher, queryStrategy)
|
||||
return NewTCPNameServer(u, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
|
||||
return NewTCPLocalNameServer(u, queryStrategy)
|
||||
return NewTCPLocalNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
case strings.EqualFold(u.String(), "fakedns"):
|
||||
return NewFakeDNSServer(), nil
|
||||
var fd dns.FakeDNSEngine
|
||||
err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
fd = fdns
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFakeDNSServer(fd), nil
|
||||
}
|
||||
}
|
||||
if dest.Network == net.Network_Unknown {
|
||||
dest.Network = net.Network_UDP
|
||||
}
|
||||
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||
return NewClassicNameServer(dest, dispatcher), nil
|
||||
return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||
}
|
||||
return nil, newError("No available name server could be created from ", dest).AtWarning()
|
||||
return nil, errors.New("No available name server could be created from ", dest).AtWarning()
|
||||
}
|
||||
|
||||
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
||||
@@ -72,25 +93,27 @@ func NewClient(
|
||||
ctx context.Context,
|
||||
ns *NameServer,
|
||||
clientIP net.IP,
|
||||
container router.GeoIPMatcherContainer,
|
||||
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||
tag string,
|
||||
ipOption dns.IPOption,
|
||||
matcherInfos *[]*DomainMatcherInfo,
|
||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error,
|
||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
|
||||
) (*Client, error) {
|
||||
client := &Client{}
|
||||
|
||||
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||
// Create a new server for each client for now
|
||||
server, err := NewServer(ns.Address.AsDestination(), dispatcher, ns.GetQueryStrategy())
|
||||
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
if err != nil {
|
||||
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
// Priotize local domains with specific TLDs or without any dot to local DNS
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||
// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
|
||||
// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||
// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
|
||||
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
|
||||
// Related issues:
|
||||
@@ -111,7 +134,8 @@ func NewClient(
|
||||
for _, domain := range ns.PrioritizedDomain {
|
||||
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||
if err != nil {
|
||||
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||
errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
|
||||
domainRule, _ = toStrMatcher(DomainMatchingType_Full, "hack.fix.index.for.illegal.domain.rule")
|
||||
}
|
||||
originalRuleIdx := ruleCurr
|
||||
if ruleCurr < len(ns.OriginalRules) {
|
||||
@@ -128,108 +152,135 @@ func NewClient(
|
||||
rules = append(rules, domainRule.String())
|
||||
ruleCurr++
|
||||
}
|
||||
err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||
if err != nil {
|
||||
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||
}
|
||||
updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||
}
|
||||
|
||||
// Establish expected IPs
|
||||
var matchers []*router.GeoIPMatcher
|
||||
for _, geoip := range ns.Geoip {
|
||||
matcher, err := container.Add(geoip)
|
||||
var expectedMatcher router.GeoIPMatcher
|
||||
if len(ns.ExpectedGeoip) > 0 {
|
||||
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
|
||||
if err != nil {
|
||||
return newError("failed to create ip matcher").Base(err).AtWarning()
|
||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
}
|
||||
|
||||
// Establish unexpected IPs
|
||||
var unexpectedMatcher router.GeoIPMatcher
|
||||
if len(ns.UnexpectedGeoip) > 0 {
|
||||
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
|
||||
if err != nil {
|
||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
switch ns.Address.Address.GetAddress().(type) {
|
||||
case *net.IPOrDomain_Domain:
|
||||
newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String())
|
||||
case *net.IPOrDomain_Ip:
|
||||
newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||
errors.LogInfo(ctx, "DNS: client ", net.IP(ns.Address.Address.GetIp()), " uses clientIP ", clientIP.String())
|
||||
}
|
||||
}
|
||||
|
||||
var timeoutMs = 4000 * time.Millisecond
|
||||
if ns.TimeoutMs > 0 {
|
||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||
}
|
||||
|
||||
checkSystem := ns.QueryStrategy == QueryStrategy_USE_SYS
|
||||
|
||||
client.server = server
|
||||
client.clientIP = clientIP
|
||||
client.skipFallback = ns.SkipFallback
|
||||
client.domains = rules
|
||||
client.expectIPs = matchers
|
||||
client.expectedIPs = expectedMatcher
|
||||
client.unexpectedIPs = unexpectedMatcher
|
||||
client.actPrior = ns.ActPrior
|
||||
client.actUnprior = ns.ActUnprior
|
||||
client.tag = tag
|
||||
client.timeoutMs = timeoutMs
|
||||
client.finalQuery = ns.FinalQuery
|
||||
client.ipOption = &ipOption
|
||||
client.checkSystem = checkSystem
|
||||
client.policyID = ns.PolicyID
|
||||
return nil
|
||||
})
|
||||
return client, err
|
||||
}
|
||||
|
||||
// NewSimpleClient creates a DNS client with a simple destination.
|
||||
func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) {
|
||||
client := &Client{}
|
||||
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||
server, err := NewServer(endpoint.AsDestination(), dispatcher, QueryStrategy_USE_IP)
|
||||
if err != nil {
|
||||
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||
}
|
||||
client.server = server
|
||||
client.clientIP = clientIP
|
||||
return nil
|
||||
})
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
switch endpoint.Address.GetAddress().(type) {
|
||||
case *net.IPOrDomain_Domain:
|
||||
newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||
case *net.IPOrDomain_Ip:
|
||||
newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||
}
|
||||
}
|
||||
|
||||
return client, err
|
||||
}
|
||||
|
||||
// Name returns the server name the client manages.
|
||||
func (c *Client) Name() string {
|
||||
return c.server.Name()
|
||||
}
|
||||
|
||||
// QueryIP sends DNS query to the name server with the client's IP.
|
||||
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||
ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
|
||||
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
if c.checkSystem {
|
||||
supportIPv4, supportIPv6 := checkRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
|
||||
option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
|
||||
}
|
||||
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
|
||||
ips, ttl, err := c.server.QueryIP(ctx, domain, option)
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
return ips, err
|
||||
return nil, 0, err
|
||||
}
|
||||
return c.MatchExpectedIPs(domain, ips)
|
||||
}
|
||||
|
||||
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
|
||||
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
|
||||
if len(c.expectIPs) == 0 {
|
||||
return ips, nil
|
||||
if len(ips) == 0 {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
newIps := []net.IP{}
|
||||
for _, ip := range ips {
|
||||
for _, matcher := range c.expectIPs {
|
||||
if matcher.Match(ip) {
|
||||
newIps = append(newIps, ip)
|
||||
break
|
||||
}
|
||||
|
||||
if c.expectedIPs != nil && !c.actPrior {
|
||||
ips, _ = c.expectedIPs.FilterIPs(ips)
|
||||
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
|
||||
if len(ips) == 0 {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
}
|
||||
if len(newIps) == 0 {
|
||||
return nil, errExpectedIPNonMatch
|
||||
|
||||
if c.unexpectedIPs != nil && !c.actUnprior {
|
||||
_, ips = c.unexpectedIPs.FilterIPs(ips)
|
||||
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
|
||||
if len(ips) == 0 {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
}
|
||||
newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog()
|
||||
return newIps, nil
|
||||
|
||||
if c.expectedIPs != nil && c.actPrior {
|
||||
ipsNew, _ := c.expectedIPs.FilterIPs(ips)
|
||||
if len(ipsNew) > 0 {
|
||||
ips = ipsNew
|
||||
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if c.unexpectedIPs != nil && c.actUnprior {
|
||||
_, ipsNew := c.unexpectedIPs.FilterIPs(ips)
|
||||
if len(ipsNew) > 0 {
|
||||
ips = ipsNew
|
||||
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return ips, ttl, nil
|
||||
}
|
||||
|
||||
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
|
||||
switch queryStrategy {
|
||||
case QueryStrategy_USE_IP:
|
||||
return ipOption
|
||||
case QueryStrategy_USE_SYS:
|
||||
return ipOption
|
||||
case QueryStrategy_USE_IP4:
|
||||
return dns.IPOption{
|
||||
IPv4Enable: ipOption.IPv4Enable,
|
||||
@@ -246,3 +297,18 @@ func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption)
|
||||
return ipOption
|
||||
}
|
||||
}
|
||||
|
||||
func ToDomainMatchingType(t router.Domain_Type) DomainMatchingType {
|
||||
switch t {
|
||||
case router.Domain_Domain:
|
||||
return DomainMatchingType_Subdomain
|
||||
case router.Domain_Full:
|
||||
return DomainMatchingType_Full
|
||||
case router.Domain_Plain:
|
||||
return DomainMatchingType_Keyword
|
||||
case router.Domain_Regex:
|
||||
return DomainMatchingType_Regex
|
||||
default:
|
||||
panic("unknown domain type")
|
||||
}
|
||||
}
|
||||
|
||||
173
app/dns/nameserver_cached.go
Normal file
173
app/dns/nameserver_cached.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
go_errors "errors"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
type CachedNameserver interface {
|
||||
getCacheController() *CacheController
|
||||
|
||||
sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns.IPOption)
|
||||
}
|
||||
|
||||
// queryIP is called from dns.Server->queryIPTimeout
|
||||
func queryIP(ctx context.Context, s CachedNameserver, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
fqdn := Fqdn(domain)
|
||||
|
||||
cache := s.getCacheController()
|
||||
if !cache.disableCache {
|
||||
if rec := cache.findRecords(fqdn); rec != nil {
|
||||
ips, ttl, err := merge(option, rec.A, rec.AAAA)
|
||||
if !go_errors.Is(err, errRecordNotFound) {
|
||||
if ttl > 0 {
|
||||
errors.LogDebugInner(ctx, err, cache.name, " cache HIT ", fqdn, " -> ", ips)
|
||||
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||
return ips, uint32(ttl), err
|
||||
}
|
||||
if cache.serveStale && (cache.serveExpiredTTL == 0 || cache.serveExpiredTTL < ttl) {
|
||||
errors.LogDebugInner(ctx, err, cache.name, " cache OPTIMISTE ", fqdn, " -> ", ips)
|
||||
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheOptimiste, Elapsed: 0, Error: err})
|
||||
go pull(ctx, s, fqdn, option)
|
||||
return ips, 1, err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", fqdn, " at ", cache.name)
|
||||
}
|
||||
|
||||
return fetch(ctx, s, fqdn, option)
|
||||
}
|
||||
|
||||
func pull(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) {
|
||||
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 8*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fetch(nctx, s, fqdn, option)
|
||||
}
|
||||
|
||||
func fetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
key := fqdn
|
||||
switch {
|
||||
case option.IPv4Enable && option.IPv6Enable:
|
||||
key = key + "46"
|
||||
case option.IPv4Enable:
|
||||
key = key + "4"
|
||||
case option.IPv6Enable:
|
||||
key = key + "6"
|
||||
}
|
||||
|
||||
v, _, _ := s.getCacheController().requestGroup.Do(key, func() (any, error) {
|
||||
return doFetch(ctx, s, fqdn, option), nil
|
||||
})
|
||||
ret := v.(result)
|
||||
|
||||
return ret.ips, ret.ttl, ret.error
|
||||
}
|
||||
|
||||
type result struct {
|
||||
ips []net.IP
|
||||
ttl uint32
|
||||
error
|
||||
}
|
||||
|
||||
func doFetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) result {
|
||||
sub4, sub6 := s.getCacheController().registerSubscribers(fqdn, option)
|
||||
defer closeSubscribers(sub4, sub6)
|
||||
|
||||
noResponseErrCh := make(chan error, 2)
|
||||
onEvent := func(sub *pubsub.Subscriber) (*IPRecord, error) {
|
||||
if sub == nil {
|
||||
return nil, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case err := <-noResponseErrCh:
|
||||
return nil, err
|
||||
case msg := <-sub.Wait():
|
||||
sub.Close()
|
||||
return msg.(*IPRecord), nil // should panic
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
||||
|
||||
rec4, err4 := onEvent(sub4)
|
||||
rec6, err6 := onEvent(sub6)
|
||||
|
||||
var errs []error
|
||||
if err4 != nil {
|
||||
errs = append(errs, err4)
|
||||
}
|
||||
if err6 != nil {
|
||||
errs = append(errs, err6)
|
||||
}
|
||||
|
||||
ips, ttl, err := merge(option, rec4, rec6, errs...)
|
||||
var rTTL uint32
|
||||
if ttl > 0 {
|
||||
rTTL = uint32(ttl)
|
||||
} else if ttl == 0 && go_errors.Is(err, errRecordNotFound) {
|
||||
rTTL = 0
|
||||
} else { // edge case: where a fast rep's ttl expires during the rtt of a slower, parallel query
|
||||
rTTL = 1
|
||||
}
|
||||
|
||||
log.Record(&log.DNSLog{Server: s.getCacheController().name, Domain: fqdn, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
return result{ips, rTTL, err}
|
||||
}
|
||||
|
||||
func merge(option dns.IPOption, rec4 *IPRecord, rec6 *IPRecord, errs ...error) ([]net.IP, int32, error) {
|
||||
var allIPs []net.IP
|
||||
var rTTL int32 = dns.DefaultTTL
|
||||
|
||||
mergeReq := option.IPv4Enable && option.IPv6Enable
|
||||
|
||||
if option.IPv4Enable {
|
||||
ips, ttl, err := rec4.getIPs() // it's safe
|
||||
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
||||
return ips, ttl, err
|
||||
}
|
||||
if ttl < rTTL {
|
||||
rTTL = ttl
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
allIPs = append(allIPs, ips...)
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if option.IPv6Enable {
|
||||
ips, ttl, err := rec6.getIPs() // it's safe
|
||||
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
||||
return ips, ttl, err
|
||||
}
|
||||
if ttl < rTTL {
|
||||
rTTL = ttl
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
allIPs = append(allIPs, ips...)
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(allIPs) > 0 {
|
||||
return allIPs, rTTL, nil
|
||||
}
|
||||
if len(errs) == 2 && go_errors.Is(errs[0], errs[1]) {
|
||||
return nil, rTTL, errs[0]
|
||||
}
|
||||
return nil, rTTL, errors.Combine(errs...)
|
||||
}
|
||||
@@ -3,236 +3,148 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/protocol/dns"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
|
||||
// which is compatible with traditional dns over udp(RFC1035),
|
||||
// thus most of the DOH implementation is copied from udpns.go
|
||||
type DoHNameServer struct {
|
||||
dispatcher routing.Dispatcher
|
||||
sync.RWMutex
|
||||
ips map[string]*record
|
||||
pub *pubsub.Service
|
||||
cleanup *task.Periodic
|
||||
reqID uint32
|
||||
httpClient *http.Client
|
||||
dohURL string
|
||||
name string
|
||||
queryStrategy QueryStrategy
|
||||
cacheController *CacheController
|
||||
httpClient *http.Client
|
||||
dohURL string
|
||||
clientIP net.IP
|
||||
}
|
||||
|
||||
// NewDoHNameServer creates DOH server object for remote resolving.
|
||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (*DoHNameServer, error) {
|
||||
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
|
||||
s := baseDOHNameServer(url, "DOH", queryStrategy)
|
||||
|
||||
s.dispatcher = dispatcher
|
||||
tr := &http.Transport{
|
||||
MaxIdleConns: 30,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 30 * time.Second,
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dest, err := net.ParseDestination(network + ":" + addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := common.ChainedClosable{}
|
||||
if cw, ok := link.Writer.(common.Closable); ok {
|
||||
cc = append(cc, cw)
|
||||
}
|
||||
if cr, ok := link.Reader.(common.Closable); ok {
|
||||
cc = append(cc, cr)
|
||||
}
|
||||
return cnc.NewConnection(
|
||||
cnc.ConnectionInputMulti(link.Writer),
|
||||
cnc.ConnectionOutputMulti(link.Reader),
|
||||
cnc.ConnectionOnClose(cc),
|
||||
), nil
|
||||
},
|
||||
}
|
||||
s.httpClient = &http.Client{
|
||||
Timeout: time.Second * 180,
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewDoHLocalNameServer creates DOH client object for local resolving
|
||||
func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameServer {
|
||||
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *DoHNameServer {
|
||||
url.Scheme = "https"
|
||||
s := baseDOHNameServer(url, "DOHL", queryStrategy)
|
||||
tr := &http.Transport{
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dest, err := net.ParseDestination(network + ":" + addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := internet.DialSystem(ctx, dest, nil)
|
||||
log.Record(&log.AccessMessage{
|
||||
From: "DNS",
|
||||
To: s.dohURL,
|
||||
Status: log.AccessAccepted,
|
||||
Detour: "local",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
mode := "DOH"
|
||||
if dispatcher == nil {
|
||||
mode = "DOHL"
|
||||
}
|
||||
errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
|
||||
s := &DoHNameServer{
|
||||
cacheController: NewCacheController(mode+"//"+url.Host, disableCache, serveStale, serveExpiredTTL),
|
||||
dohURL: url.String(),
|
||||
clientIP: clientIP,
|
||||
}
|
||||
s.httpClient = &http.Client{
|
||||
Timeout: time.Second * 180,
|
||||
Transport: tr,
|
||||
}
|
||||
newError("DNS: created Local DOH client for ", url.String()).AtInfo().WriteToLog()
|
||||
return s
|
||||
}
|
||||
|
||||
func baseDOHNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) *DoHNameServer {
|
||||
s := &DoHNameServer{
|
||||
ips: make(map[string]*record),
|
||||
pub: pubsub.NewService(),
|
||||
name: prefix + "//" + url.Host,
|
||||
dohURL: url.String(),
|
||||
queryStrategy: queryStrategy,
|
||||
}
|
||||
s.cleanup = &task.Periodic{
|
||||
Interval: time.Minute,
|
||||
Execute: s.Cleanup,
|
||||
Transport: &http2.Transport{
|
||||
IdleConnTimeout: net.ConnIdleTimeout,
|
||||
ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
dest, err := net.ParseDestination(network + ":" + addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conn net.Conn
|
||||
if dispatcher != nil {
|
||||
dnsCtx := toDnsContext(ctx, s.dohURL)
|
||||
if h2c {
|
||||
dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
|
||||
dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
|
||||
}
|
||||
link, err := dispatcher.Dispatch(dnsCtx, dest)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc := common.ChainedClosable{}
|
||||
if cw, ok := link.Writer.(common.Closable); ok {
|
||||
cc = append(cc, cw)
|
||||
}
|
||||
if cr, ok := link.Reader.(common.Closable); ok {
|
||||
cc = append(cc, cr)
|
||||
}
|
||||
conn = cnc.NewConnection(
|
||||
cnc.ConnectionInputMulti(link.Writer),
|
||||
cnc.ConnectionOutputMulti(link.Reader),
|
||||
cnc.ConnectionOnClose(cc),
|
||||
)
|
||||
} else {
|
||||
log.Record(&log.AccessMessage{
|
||||
From: "DNS",
|
||||
To: s.dohURL,
|
||||
Status: log.AccessAccepted,
|
||||
Detour: "local",
|
||||
})
|
||||
conn, err = internet.DialSystem(ctx, dest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !h2c {
|
||||
conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
|
||||
if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Name implements Server.
|
||||
func (s *DoHNameServer) Name() string {
|
||||
return s.name
|
||||
return s.cacheController.name
|
||||
}
|
||||
|
||||
// Cleanup clears expired items from cache
|
||||
func (s *DoHNameServer) Cleanup() error {
|
||||
now := time.Now()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
return newError("nothing to do. stopping...")
|
||||
}
|
||||
|
||||
for domain, record := range s.ips {
|
||||
if record.A != nil && record.A.Expire.Before(now) {
|
||||
record.A = nil
|
||||
}
|
||||
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||
record.AAAA = nil
|
||||
}
|
||||
|
||||
if record.A == nil && record.AAAA == nil {
|
||||
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||
delete(s.ips, domain)
|
||||
} else {
|
||||
s.ips[domain] = record
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
s.ips = make(map[string]*record)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||
elapsed := time.Since(req.start)
|
||||
|
||||
s.Lock()
|
||||
rec, found := s.ips[req.domain]
|
||||
if !found {
|
||||
rec = &record{}
|
||||
}
|
||||
updated := false
|
||||
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
if isNewer(rec.A, ipRec) {
|
||||
rec.A = ipRec
|
||||
updated = true
|
||||
}
|
||||
case dnsmessage.TypeAAAA:
|
||||
addr := make([]net.Address, 0, len(ipRec.IP))
|
||||
for _, ip := range ipRec.IP {
|
||||
if len(ip.IP()) == net.IPv6len {
|
||||
addr = append(addr, ip)
|
||||
}
|
||||
}
|
||||
ipRec.IP = addr
|
||||
if isNewer(rec.AAAA, ipRec) {
|
||||
rec.AAAA = ipRec
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||
|
||||
if updated {
|
||||
s.ips[req.domain] = rec
|
||||
}
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
s.pub.Publish(req.domain+"4", nil)
|
||||
case dnsmessage.TypeAAAA:
|
||||
s.pub.Publish(req.domain+"6", nil)
|
||||
}
|
||||
s.Unlock()
|
||||
common.Must(s.cleanup.Start())
|
||||
// IsDisableCache implements Server.
|
||||
func (s *DoHNameServer) IsDisableCache() bool {
|
||||
return s.cacheController.disableCache
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) newReqID() uint16 {
|
||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||
// getCacheController implements CachedNameserver.
|
||||
func (s *DoHNameServer) getCacheController() *CacheController {
|
||||
return s.cacheController
|
||||
}
|
||||
|
||||
if s.name+"." == "DOH//"+domain {
|
||||
newError(s.name, " tries to resolve itself! Use IP or set \"hosts\" instead.").AtError().WriteToLog(session.ExportIDToError(ctx))
|
||||
// sendQuery implements CachedNameserver.
|
||||
func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||
|
||||
if s.Name()+"." == "DOH//"+fqdn {
|
||||
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
||||
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
@@ -258,7 +170,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
})
|
||||
|
||||
// forced to use mux for DOH
|
||||
// dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true)
|
||||
// dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)
|
||||
|
||||
var cancel context.CancelFunc
|
||||
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||
@@ -266,20 +178,29 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
|
||||
b, err := dns.PackMessage(r.msg)
|
||||
if err != nil {
|
||||
newError("failed to pack dns query for ", domain).Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to pack dns query for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
|
||||
if err != nil {
|
||||
newError("failed to retrieve response for ", domain).Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to retrieve response for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
rec, err := parseResponse(resp)
|
||||
if err != nil {
|
||||
newError("failed to handle DOH response for ", domain).Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
s.updateIP(r, rec)
|
||||
s.cacheController.updateRecord(r, rec)
|
||||
}(req)
|
||||
}
|
||||
}
|
||||
@@ -294,6 +215,8 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
||||
req.Header.Add("Accept", "application/dns-message")
|
||||
req.Header.Add("Content-Type", "application/dns-message")
|
||||
|
||||
req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
|
||||
|
||||
hc := s.httpClient
|
||||
|
||||
resp, err := hc.Do(req.WithContext(ctx))
|
||||
@@ -310,107 +233,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||
s.RLock()
|
||||
record, found := s.ips[domain]
|
||||
s.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
var err4 error
|
||||
var err6 error
|
||||
var ips []net.Address
|
||||
var ip6 []net.Address
|
||||
|
||||
if option.IPv4Enable {
|
||||
ips, err4 = record.A.getIPs()
|
||||
}
|
||||
|
||||
if option.IPv6Enable {
|
||||
ip6, err6 = record.AAAA.getIPs()
|
||||
ips = append(ips, ip6...)
|
||||
}
|
||||
|
||||
if len(ips) > 0 {
|
||||
return toNetIP(ips)
|
||||
}
|
||||
|
||||
if err4 != nil {
|
||||
return nil, err4
|
||||
}
|
||||
|
||||
if err6 != nil {
|
||||
return nil, err6
|
||||
}
|
||||
|
||||
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
||||
fqdn := Fqdn(domain)
|
||||
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
if disableCache {
|
||||
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||
} else {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||
return ips, err
|
||||
}
|
||||
}
|
||||
|
||||
// ipv4 and ipv6 belong to different subscription groups
|
||||
var sub4, sub6 *pubsub.Subscriber
|
||||
if option.IPv4Enable {
|
||||
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||
defer sub4.Close()
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||
defer sub6.Close()
|
||||
}
|
||||
done := make(chan interface{})
|
||||
go func() {
|
||||
if sub4 != nil {
|
||||
select {
|
||||
case <-sub4.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
if sub6 != nil {
|
||||
select {
|
||||
case <-sub6.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||
start := time.Now()
|
||||
|
||||
for {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
return ips, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||
return queryIP(ctx, s, domain, option)
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ func TestDOHNameServer(t *testing.T) {
|
||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||
common.Must(err)
|
||||
|
||||
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
|
||||
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -34,12 +34,12 @@ func TestDOHNameServerWithCache(t *testing.T) {
|
||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||
common.Must(err)
|
||||
|
||||
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
|
||||
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -47,10 +47,10 @@ func TestDOHNameServerWithCache(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, true)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(ips2, ips); r != "" {
|
||||
@@ -62,12 +62,12 @@ func TestDOHNameServerWithIPv4Override(t *testing.T) {
|
||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||
common.Must(err)
|
||||
|
||||
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP4)
|
||||
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
IPv6Enable: false,
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -85,12 +85,12 @@ func TestDOHNameServerWithIPv6Override(t *testing.T) {
|
||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||
common.Must(err)
|
||||
|
||||
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP6)
|
||||
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
|
||||
@@ -3,8 +3,8 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
@@ -12,22 +12,24 @@ type FakeDNSServer struct {
|
||||
fakeDNSEngine dns.FakeDNSEngine
|
||||
}
|
||||
|
||||
func NewFakeDNSServer() *FakeDNSServer {
|
||||
return &FakeDNSServer{}
|
||||
func NewFakeDNSServer(fd dns.FakeDNSEngine) *FakeDNSServer {
|
||||
return &FakeDNSServer{fakeDNSEngine: fd}
|
||||
}
|
||||
|
||||
func (FakeDNSServer) Name() string {
|
||||
return "FakeDNS"
|
||||
}
|
||||
|
||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, error) {
|
||||
// IsDisableCache implements Server.
|
||||
func (s *FakeDNSServer) IsDisableCache() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
|
||||
if f.fakeDNSEngine == nil {
|
||||
if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
|
||||
f.fakeDNSEngine = fd
|
||||
}); err != nil {
|
||||
return nil, newError("Unable to locate a fake DNS Engine").Base(err).AtError()
|
||||
}
|
||||
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
|
||||
}
|
||||
|
||||
var ips []net.Address
|
||||
if fkr0, ok := f.fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||
ips = fkr0.GetFakeIPForDomain3(domain, opt.IPv4Enable, opt.IPv6Enable)
|
||||
@@ -37,13 +39,13 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, op
|
||||
|
||||
netIP, err := toNetIP(ips)
|
||||
if err != nil {
|
||||
return nil, newError("Unable to convert IP to net ip").Base(err).AtError()
|
||||
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
|
||||
}
|
||||
|
||||
newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
||||
|
||||
if len(netIP) > 0 {
|
||||
return netIP, nil
|
||||
return netIP, 1, nil // fakeIP ttl is 1
|
||||
}
|
||||
return nil, dns.ErrEmptyResponse
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
@@ -16,19 +16,14 @@ type LocalNameServer struct {
|
||||
client *localdns.Client
|
||||
}
|
||||
|
||||
const errEmptyResponse = "No address associated with hostname"
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, err error) {
|
||||
start := time.Now()
|
||||
ips, err = s.client.LookupIP(domain, option)
|
||||
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
||||
|
||||
if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
|
||||
err = dns.ErrEmptyResponse
|
||||
}
|
||||
start := time.Now()
|
||||
ips, ttl, err = s.client.LookupIP(domain, option)
|
||||
|
||||
if len(ips) > 0 {
|
||||
newError("Localhost got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||
errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips)
|
||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
}
|
||||
|
||||
@@ -40,15 +35,20 @@ func (s *LocalNameServer) Name() string {
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
// IsDisableCache implements Server.
|
||||
func (s *LocalNameServer) IsDisableCache() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
||||
func NewLocalNameServer() *LocalNameServer {
|
||||
newError("DNS: created localhost client").AtInfo().WriteToLog()
|
||||
errors.LogInfo(context.Background(), "DNS: created localhost client")
|
||||
return &LocalNameServer{
|
||||
client: localdns.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
||||
func NewLocalDNSClient() *Client {
|
||||
return &Client{server: NewLocalNameServer()}
|
||||
func NewLocalDNSClient(ipOption dns.IPOption) *Client {
|
||||
return &Client{server: NewLocalNameServer(), ipOption: &ipOption}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,17 @@ import (
|
||||
|
||||
. "github.com/xtls/xray-core/app/dns"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
func TestLocalNameServer(t *testing.T) {
|
||||
s := NewLocalNameServer()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
|
||||
@@ -6,21 +6,17 @@ import (
|
||||
"encoding/binary"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/dns"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@@ -33,20 +29,14 @@ const handshakeTimeout = time.Second * 8
|
||||
// QUICNameServer implemented DNS over QUIC
|
||||
type QUICNameServer struct {
|
||||
sync.RWMutex
|
||||
ips map[string]*record
|
||||
pub *pubsub.Service
|
||||
cleanup *task.Periodic
|
||||
reqID uint32
|
||||
name string
|
||||
destination *net.Destination
|
||||
connection quic.Connection
|
||||
queryStrategy QueryStrategy
|
||||
cacheController *CacheController
|
||||
destination *net.Destination
|
||||
connection *quic.Conn
|
||||
clientIP net.IP
|
||||
}
|
||||
|
||||
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
||||
func NewQUICNameServer(url *url.URL, queryStrategy QueryStrategy) (*QUICNameServer, error) {
|
||||
newError("DNS: created Local DNS-over-QUIC client for ", url.String()).AtInfo().WriteToLog()
|
||||
|
||||
func NewQUICNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*QUICNameServer, error) {
|
||||
var err error
|
||||
port := net.Port(853)
|
||||
if url.Port() != "" {
|
||||
@@ -58,110 +48,37 @@ func NewQUICNameServer(url *url.URL, queryStrategy QueryStrategy) (*QUICNameServ
|
||||
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
|
||||
|
||||
s := &QUICNameServer{
|
||||
ips: make(map[string]*record),
|
||||
pub: pubsub.NewService(),
|
||||
name: url.String(),
|
||||
destination: &dest,
|
||||
queryStrategy: queryStrategy,
|
||||
}
|
||||
s.cleanup = &task.Periodic{
|
||||
Interval: time.Minute,
|
||||
Execute: s.Cleanup,
|
||||
cacheController: NewCacheController(url.String(), disableCache, serveStale, serveExpiredTTL),
|
||||
destination: &dest,
|
||||
clientIP: clientIP,
|
||||
}
|
||||
|
||||
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Name returns client name
|
||||
// Name implements Server.
|
||||
func (s *QUICNameServer) Name() string {
|
||||
return s.name
|
||||
return s.cacheController.name
|
||||
}
|
||||
|
||||
// Cleanup clears expired items from cache
|
||||
func (s *QUICNameServer) Cleanup() error {
|
||||
now := time.Now()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
return newError("nothing to do. stopping...")
|
||||
}
|
||||
|
||||
for domain, record := range s.ips {
|
||||
if record.A != nil && record.A.Expire.Before(now) {
|
||||
record.A = nil
|
||||
}
|
||||
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||
record.AAAA = nil
|
||||
}
|
||||
|
||||
if record.A == nil && record.AAAA == nil {
|
||||
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||
delete(s.ips, domain)
|
||||
} else {
|
||||
s.ips[domain] = record
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
s.ips = make(map[string]*record)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||
elapsed := time.Since(req.start)
|
||||
|
||||
s.Lock()
|
||||
rec, found := s.ips[req.domain]
|
||||
if !found {
|
||||
rec = &record{}
|
||||
}
|
||||
updated := false
|
||||
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
if isNewer(rec.A, ipRec) {
|
||||
rec.A = ipRec
|
||||
updated = true
|
||||
}
|
||||
case dnsmessage.TypeAAAA:
|
||||
addr := make([]net.Address, 0)
|
||||
for _, ip := range ipRec.IP {
|
||||
if len(ip.IP()) == net.IPv6len {
|
||||
addr = append(addr, ip)
|
||||
}
|
||||
}
|
||||
ipRec.IP = addr
|
||||
if isNewer(rec.AAAA, ipRec) {
|
||||
rec.AAAA = ipRec
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||
|
||||
if updated {
|
||||
s.ips[req.domain] = rec
|
||||
}
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
s.pub.Publish(req.domain+"4", nil)
|
||||
case dnsmessage.TypeAAAA:
|
||||
s.pub.Publish(req.domain+"6", nil)
|
||||
}
|
||||
s.Unlock()
|
||||
common.Must(s.cleanup.Start())
|
||||
// IsDisableCache implements Server.
|
||||
func (s *QUICNameServer) IsDisableCache() bool {
|
||||
return s.cacheController.disableCache
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) newReqID() uint16 {
|
||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||
// getCacheController implements CachedNameServer.
|
||||
func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheController }
|
||||
|
||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||
// sendQuery implements CachedNameServer.
|
||||
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
@@ -192,24 +109,47 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
|
||||
|
||||
b, err := dns.PackMessage(r.msg)
|
||||
if err != nil {
|
||||
newError("failed to pack dns query").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dnsReqBuf := buf.New()
|
||||
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||
dnsReqBuf.Write(b.Bytes())
|
||||
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "binary write failed")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err = dnsReqBuf.Write(b.Bytes())
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "buffer write failed")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
b.Release()
|
||||
|
||||
conn, err := s.openStream(dnsCtx)
|
||||
if err != nil {
|
||||
newError("failed to open quic connection").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to open quic connection")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||
if err != nil {
|
||||
newError("failed to send query").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,138 +159,50 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
|
||||
defer respBuf.Release()
|
||||
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||
if err != nil && n == 0 {
|
||||
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
var length int16
|
||||
var length uint16
|
||||
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
newError("failed to parse response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
respBuf.Clear()
|
||||
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||
if err != nil && n == 0 {
|
||||
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rec, err := parseResponse(respBuf.Bytes())
|
||||
if err != nil {
|
||||
newError("failed to handle response").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to handle response")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
s.updateIP(r, rec)
|
||||
s.cacheController.updateRecord(r, rec)
|
||||
}(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||
s.RLock()
|
||||
record, found := s.ips[domain]
|
||||
s.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
var err4 error
|
||||
var err6 error
|
||||
var ips []net.Address
|
||||
var ip6 []net.Address
|
||||
|
||||
if option.IPv4Enable {
|
||||
ips, err4 = record.A.getIPs()
|
||||
}
|
||||
|
||||
if option.IPv6Enable {
|
||||
ip6, err6 = record.AAAA.getIPs()
|
||||
ips = append(ips, ip6...)
|
||||
}
|
||||
|
||||
if len(ips) > 0 {
|
||||
return toNetIP(ips)
|
||||
}
|
||||
|
||||
if err4 != nil {
|
||||
return nil, err4
|
||||
}
|
||||
|
||||
if err6 != nil {
|
||||
return nil, err6
|
||||
}
|
||||
|
||||
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
return nil, errRecordNotFound
|
||||
// QueryIP implements Server.
|
||||
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||
return queryIP(ctx, s, domain, option)
|
||||
}
|
||||
|
||||
// QueryIP is called from dns.Server->queryIPTimeout
|
||||
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||
fqdn := Fqdn(domain)
|
||||
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
if disableCache {
|
||||
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||
} else {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||
return ips, err
|
||||
}
|
||||
}
|
||||
|
||||
// ipv4 and ipv6 belong to different subscription groups
|
||||
var sub4, sub6 *pubsub.Subscriber
|
||||
if option.IPv4Enable {
|
||||
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||
defer sub4.Close()
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||
defer sub6.Close()
|
||||
}
|
||||
done := make(chan interface{})
|
||||
go func() {
|
||||
if sub4 != nil {
|
||||
select {
|
||||
case <-sub4.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
if sub6 != nil {
|
||||
select {
|
||||
case <-sub6.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||
start := time.Now()
|
||||
|
||||
for {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
return ips, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isActive(s quic.Connection) bool {
|
||||
func isActive(s *quic.Conn) bool {
|
||||
select {
|
||||
case <-s.Context().Done():
|
||||
return false
|
||||
@@ -359,8 +211,8 @@ func isActive(s quic.Connection) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) getConnection() (quic.Connection, error) {
|
||||
var conn quic.Connection
|
||||
func (s *QUICNameServer) getConnection() (*quic.Conn, error) {
|
||||
var conn *quic.Conn
|
||||
s.RLock()
|
||||
conn = s.connection
|
||||
if conn != nil && isActive(conn) {
|
||||
@@ -393,7 +245,7 @@ func (s *QUICNameServer) getConnection() (quic.Connection, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) openConnection() (quic.Connection, error) {
|
||||
func (s *QUICNameServer) openConnection() (*quic.Conn, error) {
|
||||
tlsConfig := tls.Config{}
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeIdleTimeout: handshakeTimeout,
|
||||
@@ -413,7 +265,7 @@ func (s *QUICNameServer) openConnection() (quic.Connection, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *QUICNameServer) openStream(ctx context.Context) (quic.Stream, error) {
|
||||
func (s *QUICNameServer) openStream(ctx context.Context) (*quic.Stream, error) {
|
||||
conn, err := s.getConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -14,26 +14,25 @@ import (
|
||||
)
|
||||
|
||||
func TestQUICNameServer(t *testing.T) {
|
||||
url, err := url.Parse("quic://dns.adguard.com")
|
||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||
common.Must(err)
|
||||
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP)
|
||||
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
t.Error("expect some ips, but got 0")
|
||||
}
|
||||
|
||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
|
||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, true)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(ips2, ips); r != "" {
|
||||
@@ -42,15 +41,15 @@ func TestQUICNameServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
||||
url, err := url.Parse("quic://dns.adguard.com")
|
||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||
common.Must(err)
|
||||
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP4)
|
||||
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
IPv6Enable: false,
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -65,15 +64,15 @@ func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQUICNameServerWithIPv6Override(t *testing.T) {
|
||||
url, err := url.Parse("quic://dns.adguard.com")
|
||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||
common.Must(err)
|
||||
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP6)
|
||||
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
|
||||
@@ -5,45 +5,37 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/protocol/dns"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
)
|
||||
|
||||
// TCPNameServer implemented DNS over TCP (RFC7766).
|
||||
type TCPNameServer struct {
|
||||
sync.RWMutex
|
||||
name string
|
||||
destination *net.Destination
|
||||
ips map[string]*record
|
||||
pub *pubsub.Service
|
||||
cleanup *task.Periodic
|
||||
reqID uint32
|
||||
dial func(context.Context) (net.Conn, error)
|
||||
queryStrategy QueryStrategy
|
||||
cacheController *CacheController
|
||||
destination *net.Destination
|
||||
reqID uint32
|
||||
dial func(context.Context) (net.Conn, error)
|
||||
clientIP net.IP
|
||||
}
|
||||
|
||||
// NewTCPNameServer creates DNS over TCP server object for remote resolving.
|
||||
func NewTCPNameServer(
|
||||
url *url.URL,
|
||||
dispatcher routing.Dispatcher,
|
||||
queryStrategy QueryStrategy,
|
||||
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||
clientIP net.IP,
|
||||
) (*TCPNameServer, error) {
|
||||
s, err := baseTCPNameServer(url, "TCP", queryStrategy)
|
||||
s, err := baseTCPNameServer(url, "TCP", disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,12 +52,13 @@ func NewTCPNameServer(
|
||||
), nil
|
||||
}
|
||||
|
||||
errors.LogInfo(context.Background(), "DNS: created TCP client initialized for ", url.String())
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving
|
||||
func NewTCPLocalNameServer(url *url.URL, queryStrategy QueryStrategy) (*TCPNameServer, error) {
|
||||
s, err := baseTCPNameServer(url, "TCPL", queryStrategy)
|
||||
func NewTCPLocalNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
|
||||
s, err := baseTCPNameServer(url, "TCPL", disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,10 +67,11 @@ func NewTCPLocalNameServer(url *url.URL, queryStrategy QueryStrategy) (*TCPNameS
|
||||
return internet.DialSystem(ctx, *s.destination, nil)
|
||||
}
|
||||
|
||||
errors.LogInfo(context.Background(), "DNS: created Local TCP client initialized for ", url.String())
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) (*TCPNameServer, error) {
|
||||
func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
|
||||
port := net.Port(53)
|
||||
if url.Port() != "" {
|
||||
var err error
|
||||
@@ -88,15 +82,9 @@ func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy)
|
||||
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
|
||||
|
||||
s := &TCPNameServer{
|
||||
destination: &dest,
|
||||
ips: make(map[string]*record),
|
||||
pub: pubsub.NewService(),
|
||||
name: prefix + "//" + dest.NetAddr(),
|
||||
queryStrategy: queryStrategy,
|
||||
}
|
||||
s.cleanup = &task.Periodic{
|
||||
Interval: time.Minute,
|
||||
Execute: s.Cleanup,
|
||||
cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache, serveStale, serveExpiredTTL),
|
||||
destination: &dest,
|
||||
clientIP: clientIP,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -104,94 +92,28 @@ func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy)
|
||||
|
||||
// Name implements Server.
|
||||
func (s *TCPNameServer) Name() string {
|
||||
return s.name
|
||||
return s.cacheController.name
|
||||
}
|
||||
|
||||
// Cleanup clears expired items from cache
|
||||
func (s *TCPNameServer) Cleanup() error {
|
||||
now := time.Now()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
return newError("nothing to do. stopping...")
|
||||
}
|
||||
|
||||
for domain, record := range s.ips {
|
||||
if record.A != nil && record.A.Expire.Before(now) {
|
||||
record.A = nil
|
||||
}
|
||||
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||
record.AAAA = nil
|
||||
}
|
||||
|
||||
if record.A == nil && record.AAAA == nil {
|
||||
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||
delete(s.ips, domain)
|
||||
} else {
|
||||
s.ips[domain] = record
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
s.ips = make(map[string]*record)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TCPNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||
elapsed := time.Since(req.start)
|
||||
|
||||
s.Lock()
|
||||
rec, found := s.ips[req.domain]
|
||||
if !found {
|
||||
rec = &record{}
|
||||
}
|
||||
updated := false
|
||||
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
if isNewer(rec.A, ipRec) {
|
||||
rec.A = ipRec
|
||||
updated = true
|
||||
}
|
||||
case dnsmessage.TypeAAAA:
|
||||
addr := make([]net.Address, 0)
|
||||
for _, ip := range ipRec.IP {
|
||||
if len(ip.IP()) == net.IPv6len {
|
||||
addr = append(addr, ip)
|
||||
}
|
||||
}
|
||||
ipRec.IP = addr
|
||||
if isNewer(rec.AAAA, ipRec) {
|
||||
rec.AAAA = ipRec
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||
|
||||
if updated {
|
||||
s.ips[req.domain] = rec
|
||||
}
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
s.pub.Publish(req.domain+"4", nil)
|
||||
case dnsmessage.TypeAAAA:
|
||||
s.pub.Publish(req.domain+"6", nil)
|
||||
}
|
||||
s.Unlock()
|
||||
common.Must(s.cleanup.Start())
|
||||
// IsDisableCache implements Server.
|
||||
func (s *TCPNameServer) IsDisableCache() bool {
|
||||
return s.cacheController.disableCache
|
||||
}
|
||||
|
||||
func (s *TCPNameServer) newReqID() uint16 {
|
||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||
}
|
||||
|
||||
func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||
// getCacheController implements CachedNameserver.
|
||||
func (s *TCPNameServer) getCacheController() *CacheController {
|
||||
return s.cacheController
|
||||
}
|
||||
|
||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||
// sendQuery implements CachedNameserver.
|
||||
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
@@ -219,24 +141,47 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
|
||||
b, err := dns.PackMessage(r.msg)
|
||||
if err != nil {
|
||||
newError("failed to pack dns query").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := s.dial(dnsCtx)
|
||||
if err != nil {
|
||||
newError("failed to dial namesever").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to dial namesever")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
dnsReqBuf := buf.New()
|
||||
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||
dnsReqBuf.Write(b.Bytes())
|
||||
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "binary write failed")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err = dnsReqBuf.Write(b.Bytes())
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "buffer write failed")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
b.Release()
|
||||
|
||||
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||
if err != nil {
|
||||
newError("failed to send query").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
dnsReqBuf.Release()
|
||||
@@ -245,130 +190,46 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
defer respBuf.Release()
|
||||
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||
if err != nil && n == 0 {
|
||||
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
var length int16
|
||||
var length uint16
|
||||
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
newError("failed to parse response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
respBuf.Clear()
|
||||
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||
if err != nil && n == 0 {
|
||||
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rec, err := parseResponse(respBuf.Bytes())
|
||||
if err != nil {
|
||||
newError("failed to parse DNS over TCP response").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s.updateIP(r, rec)
|
||||
s.cacheController.updateRecord(r, rec)
|
||||
}(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||
s.RLock()
|
||||
record, found := s.ips[domain]
|
||||
s.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
var err4 error
|
||||
var err6 error
|
||||
var ips []net.Address
|
||||
var ip6 []net.Address
|
||||
|
||||
if option.IPv4Enable {
|
||||
ips, err4 = record.A.getIPs()
|
||||
}
|
||||
|
||||
if option.IPv6Enable {
|
||||
ip6, err6 = record.AAAA.getIPs()
|
||||
ips = append(ips, ip6...)
|
||||
}
|
||||
|
||||
if len(ips) > 0 {
|
||||
return toNetIP(ips)
|
||||
}
|
||||
|
||||
if err4 != nil {
|
||||
return nil, err4
|
||||
}
|
||||
|
||||
if err6 != nil {
|
||||
return nil, err6
|
||||
}
|
||||
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||
fqdn := Fqdn(domain)
|
||||
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||
if !option.IPv4Enable && !option.IPv6Enable {
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
if disableCache {
|
||||
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||
} else {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||
return ips, err
|
||||
}
|
||||
}
|
||||
|
||||
// ipv4 and ipv6 belong to different subscription groups
|
||||
var sub4, sub6 *pubsub.Subscriber
|
||||
if option.IPv4Enable {
|
||||
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||
defer sub4.Close()
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||
defer sub6.Close()
|
||||
}
|
||||
done := make(chan interface{})
|
||||
go func() {
|
||||
if sub4 != nil {
|
||||
select {
|
||||
case <-sub4.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
if sub6 != nil {
|
||||
select {
|
||||
case <-sub6.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||
start := time.Now()
|
||||
|
||||
for {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
return ips, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||
return queryIP(ctx, s, domain, option)
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
func TestTCPLocalNameServer(t *testing.T) {
|
||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||
common.Must(err)
|
||||
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
|
||||
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -33,13 +33,13 @@ func TestTCPLocalNameServer(t *testing.T) {
|
||||
func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||
common.Must(err)
|
||||
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
|
||||
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if len(ips) == 0 {
|
||||
@@ -47,10 +47,10 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, true)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(ips2, ips); r != "" {
|
||||
@@ -61,13 +61,13 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||
func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||
common.Must(err)
|
||||
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP4)
|
||||
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
IPv6Enable: false,
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
|
||||
@@ -85,13 +85,13 @@ func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
||||
func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
|
||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||
common.Must(err)
|
||||
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP6)
|
||||
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||
common.Must(err)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
}, false)
|
||||
})
|
||||
cancel()
|
||||
common.Must(err)
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/dns"
|
||||
udp_proto "github.com/xtls/xray-core/common/protocol/udp"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
@@ -24,72 +22,61 @@ import (
|
||||
// ClassicNameServer implemented traditional UDP DNS.
|
||||
type ClassicNameServer struct {
|
||||
sync.RWMutex
|
||||
name string
|
||||
address *net.Destination
|
||||
ips map[string]*record
|
||||
requests map[uint16]*dnsRequest
|
||||
pub *pubsub.Service
|
||||
udpServer *udp.Dispatcher
|
||||
cleanup *task.Periodic
|
||||
reqID uint32
|
||||
cacheController *CacheController
|
||||
address *net.Destination
|
||||
requests map[uint16]*udpDnsRequest
|
||||
udpServer *udp.Dispatcher
|
||||
requestsCleanup *task.Periodic
|
||||
reqID uint32
|
||||
clientIP net.IP
|
||||
}
|
||||
|
||||
type udpDnsRequest struct {
|
||||
dnsRequest
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewClassicNameServer creates udp server object for remote resolving.
|
||||
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher) *ClassicNameServer {
|
||||
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *ClassicNameServer {
|
||||
// default to 53 if unspecific
|
||||
if address.Port == 0 {
|
||||
address.Port = net.Port(53)
|
||||
}
|
||||
|
||||
s := &ClassicNameServer{
|
||||
address: &address,
|
||||
ips: make(map[string]*record),
|
||||
requests: make(map[uint16]*dnsRequest),
|
||||
pub: pubsub.NewService(),
|
||||
name: strings.ToUpper(address.String()),
|
||||
cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache, serveStale, serveExpiredTTL),
|
||||
address: &address,
|
||||
requests: make(map[uint16]*udpDnsRequest),
|
||||
clientIP: clientIP,
|
||||
}
|
||||
s.cleanup = &task.Periodic{
|
||||
s.requestsCleanup = &task.Periodic{
|
||||
Interval: time.Minute,
|
||||
Execute: s.Cleanup,
|
||||
Execute: s.RequestsCleanup,
|
||||
}
|
||||
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
|
||||
newError("DNS: created UDP client initialized for ", address.NetAddr()).AtInfo().WriteToLog()
|
||||
|
||||
errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr())
|
||||
return s
|
||||
}
|
||||
|
||||
// Name implements Server.
|
||||
func (s *ClassicNameServer) Name() string {
|
||||
return s.name
|
||||
return s.cacheController.name
|
||||
}
|
||||
|
||||
// Cleanup clears expired items from cache
|
||||
func (s *ClassicNameServer) Cleanup() error {
|
||||
// IsDisableCache implements Server.
|
||||
func (s *ClassicNameServer) IsDisableCache() bool {
|
||||
return s.cacheController.disableCache
|
||||
}
|
||||
|
||||
// RequestsCleanup clears expired items from cache
|
||||
func (s *ClassicNameServer) RequestsCleanup() error {
|
||||
now := time.Now()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.ips) == 0 && len(s.requests) == 0 {
|
||||
return newError(s.name, " nothing to do. stopping...")
|
||||
}
|
||||
|
||||
for domain, record := range s.ips {
|
||||
if record.A != nil && record.A.Expire.Before(now) {
|
||||
record.A = nil
|
||||
}
|
||||
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||
record.AAAA = nil
|
||||
}
|
||||
|
||||
if record.A == nil && record.AAAA == nil {
|
||||
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||
delete(s.ips, domain)
|
||||
} else {
|
||||
s.ips[domain] = record
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ips) == 0 {
|
||||
s.ips = make(map[string]*record)
|
||||
if len(s.requests) == 0 {
|
||||
return errors.New(s.Name(), " nothing to do. stopping...")
|
||||
}
|
||||
|
||||
for id, req := range s.requests {
|
||||
@@ -99,7 +86,7 @@ func (s *ClassicNameServer) Cleanup() error {
|
||||
}
|
||||
|
||||
if len(s.requests) == 0 {
|
||||
s.requests = make(map[uint16]*dnsRequest)
|
||||
s.requests = make(map[uint16]*udpDnsRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -107,9 +94,11 @@ func (s *ClassicNameServer) Cleanup() error {
|
||||
|
||||
// HandleResponse handles udp response packet from remote DNS server.
|
||||
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||
ipRec, err := parseResponse(packet.Payload.Bytes())
|
||||
payload := packet.Payload
|
||||
ipRec, err := parseResponse(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
newError(s.name, " fail to parse responded DNS udp").AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, s.Name(), " fail to parse responded DNS udp")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -122,175 +111,81 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
||||
}
|
||||
s.Unlock()
|
||||
if !ok {
|
||||
newError(s.name, " cannot find the pending request").AtError().WriteToLog()
|
||||
errors.LogErrorInner(ctx, err, s.Name(), " cannot find the pending request")
|
||||
return
|
||||
}
|
||||
|
||||
var rec record
|
||||
switch req.reqType {
|
||||
case dnsmessage.TypeA:
|
||||
rec.A = ipRec
|
||||
case dnsmessage.TypeAAAA:
|
||||
rec.AAAA = ipRec
|
||||
// if truncated, retry with EDNS0 option(udp payload size: 1350)
|
||||
if ipRec.RawHeader.Truncated {
|
||||
// if already has EDNS0 option, no need to retry
|
||||
if len(req.msg.Additionals) == 0 {
|
||||
// copy necessary meta data from original request
|
||||
// and add EDNS0 option
|
||||
opt := new(dnsmessage.Resource)
|
||||
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
||||
opt.Body = &dnsmessage.OPTResource{}
|
||||
newMsg := *req.msg
|
||||
newReq := *req
|
||||
newMsg.Additionals = append(newMsg.Additionals, *opt)
|
||||
newMsg.ID = s.newReqID()
|
||||
newReq.msg = &newMsg
|
||||
s.addPendingRequest(&newReq)
|
||||
b, _ := dns.PackMessage(newReq.msg)
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(req.start)
|
||||
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
||||
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
|
||||
s.updateIP(req.domain, &rec)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ClassicNameServer) updateIP(domain string, newRec *record) {
|
||||
s.Lock()
|
||||
|
||||
rec, found := s.ips[domain]
|
||||
if !found {
|
||||
rec = &record{}
|
||||
}
|
||||
|
||||
updated := false
|
||||
if isNewer(rec.A, newRec.A) {
|
||||
rec.A = newRec.A
|
||||
updated = true
|
||||
}
|
||||
if isNewer(rec.AAAA, newRec.AAAA) {
|
||||
rec.AAAA = newRec.AAAA
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated {
|
||||
newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog()
|
||||
s.ips[domain] = rec
|
||||
}
|
||||
if newRec.A != nil {
|
||||
s.pub.Publish(domain+"4", nil)
|
||||
}
|
||||
if newRec.AAAA != nil {
|
||||
s.pub.Publish(domain+"6", nil)
|
||||
}
|
||||
s.Unlock()
|
||||
common.Must(s.cleanup.Start())
|
||||
s.cacheController.updateRecord(&req.dnsRequest, ipRec)
|
||||
}
|
||||
|
||||
func (s *ClassicNameServer) newReqID() uint16 {
|
||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||
}
|
||||
|
||||
func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
|
||||
func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
id := req.msg.ID
|
||||
req.expire = time.Now().Add(time.Second * 8)
|
||||
s.requests[id] = req
|
||||
s.Unlock()
|
||||
common.Must(s.requestsCleanup.Start())
|
||||
}
|
||||
|
||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||
// getCacheController implements CachedNameserver.
|
||||
func (s *ClassicNameServer) getCacheController() *CacheController {
|
||||
return s.cacheController
|
||||
}
|
||||
|
||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||
// sendQuery implements CachedNameserver.
|
||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
for _, req := range reqs {
|
||||
s.addPendingRequest(req)
|
||||
b, _ := dns.PackMessage(req.msg)
|
||||
udpReq := &udpDnsRequest{
|
||||
dnsRequest: *req,
|
||||
ctx: ctx,
|
||||
}
|
||||
s.addPendingRequest(udpReq)
|
||||
b, err := dns.PackMessage(req.msg)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||
s.RLock()
|
||||
record, found := s.ips[domain]
|
||||
s.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, errRecordNotFound
|
||||
}
|
||||
|
||||
var err4 error
|
||||
var err6 error
|
||||
var ips []net.Address
|
||||
var ip6 []net.Address
|
||||
|
||||
if option.IPv4Enable {
|
||||
ips, err4 = record.A.getIPs()
|
||||
}
|
||||
|
||||
if option.IPv6Enable {
|
||||
ip6, err6 = record.AAAA.getIPs()
|
||||
ips = append(ips, ip6...)
|
||||
}
|
||||
|
||||
if len(ips) > 0 {
|
||||
return toNetIP(ips)
|
||||
}
|
||||
|
||||
if err4 != nil {
|
||||
return nil, err4
|
||||
}
|
||||
|
||||
if err6 != nil {
|
||||
return nil, err6
|
||||
}
|
||||
|
||||
return nil, dns_feature.ErrEmptyResponse
|
||||
}
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||
fqdn := Fqdn(domain)
|
||||
|
||||
if disableCache {
|
||||
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||
} else {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||
return ips, err
|
||||
}
|
||||
}
|
||||
|
||||
// ipv4 and ipv6 belong to different subscription groups
|
||||
var sub4, sub6 *pubsub.Subscriber
|
||||
if option.IPv4Enable {
|
||||
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||
defer sub4.Close()
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||
defer sub6.Close()
|
||||
}
|
||||
done := make(chan interface{})
|
||||
go func() {
|
||||
if sub4 != nil {
|
||||
select {
|
||||
case <-sub4.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
if sub6 != nil {
|
||||
select {
|
||||
case <-sub6.Wait():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||
start := time.Now()
|
||||
|
||||
for {
|
||||
ips, err := s.findIPsForDomain(fqdn, option)
|
||||
if err != errRecordNotFound {
|
||||
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||
return ips, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||
return queryIP(ctx, s, domain, option)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package command
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/app/log"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/core"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
@@ -19,13 +18,13 @@ type LoggerServer struct {
|
||||
func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
||||
logger := s.V.GetFeature((*log.Instance)(nil))
|
||||
if logger == nil {
|
||||
return nil, newError("unable to get logger instance")
|
||||
return nil, errors.New("unable to get logger instance")
|
||||
}
|
||||
if err := logger.Close(); err != nil {
|
||||
return nil, newError("failed to close logger").Base(err)
|
||||
return nil, errors.New("failed to close logger").Base(err)
|
||||
}
|
||||
if err := logger.Start(); err != nil {
|
||||
return nil, newError("failed to start logger").Base(err)
|
||||
return nil, errors.New("failed to start logger").Base(err)
|
||||
}
|
||||
return &RestartLoggerResponse{}, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/log/command/config.proto
|
||||
|
||||
package command
|
||||
@@ -28,11 +28,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_log_command_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -43,7 +41,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -66,11 +64,9 @@ type RestartLoggerRequest struct {
|
||||
|
||||
func (x *RestartLoggerRequest) Reset() {
|
||||
*x = RestartLoggerRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_log_command_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RestartLoggerRequest) String() string {
|
||||
@@ -81,7 +77,7 @@ func (*RestartLoggerRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RestartLoggerRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -104,11 +100,9 @@ type RestartLoggerResponse struct {
|
||||
|
||||
func (x *RestartLoggerResponse) Reset() {
|
||||
*x = RestartLoggerResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_log_command_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RestartLoggerResponse) String() string {
|
||||
@@ -119,7 +113,7 @@ func (*RestartLoggerResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RestartLoggerResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_log_command_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -174,7 +168,7 @@ func file_app_log_command_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_app_log_command_config_proto_goTypes = []interface{}{
|
||||
var file_app_log_command_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.app.log.command.Config
|
||||
(*RestartLoggerRequest)(nil), // 1: xray.app.log.command.RestartLoggerRequest
|
||||
(*RestartLoggerResponse)(nil), // 2: xray.app.log.command.RestartLoggerResponse
|
||||
@@ -194,44 +188,6 @@ func file_app_log_command_config_proto_init() {
|
||||
if File_app_log_command_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_log_command_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_log_command_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RestartLoggerRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_log_command_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RestartLoggerResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v5.27.0
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.28.2
|
||||
// source: app/log/command/config.proto
|
||||
|
||||
package command
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
LoggerService_RestartLogger_FullMethodName = "/xray.app.log.command.LoggerService/RestartLogger"
|
||||
@@ -38,8 +38,9 @@ func NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient {
|
||||
}
|
||||
|
||||
func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RestartLoggerResponse)
|
||||
err := c.cc.Invoke(ctx, LoggerService_RestartLogger_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, LoggerService_RestartLogger_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,20 +49,24 @@ func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLogg
|
||||
|
||||
// LoggerServiceServer is the server API for LoggerService service.
|
||||
// All implementations must embed UnimplementedLoggerServiceServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
type LoggerServiceServer interface {
|
||||
RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
|
||||
mustEmbedUnimplementedLoggerServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedLoggerServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedLoggerServiceServer struct {
|
||||
}
|
||||
// UnimplementedLoggerServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedLoggerServiceServer struct{}
|
||||
|
||||
func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented")
|
||||
}
|
||||
func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
||||
func (UnimplementedLoggerServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to LoggerServiceServer will
|
||||
@@ -71,6 +76,13 @@ type UnsafeLoggerServiceServer interface {
|
||||
}
|
||||
|
||||
func RegisterLoggerServiceServer(s grpc.ServiceRegistrar, srv LoggerServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedLoggerServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&LoggerService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package command
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/log/config.proto
|
||||
|
||||
package log
|
||||
@@ -84,15 +84,14 @@ type Config struct {
|
||||
AccessLogType LogType `protobuf:"varint,4,opt,name=access_log_type,json=accessLogType,proto3,enum=xray.app.log.LogType" json:"access_log_type,omitempty"`
|
||||
AccessLogPath string `protobuf:"bytes,5,opt,name=access_log_path,json=accessLogPath,proto3" json:"access_log_path,omitempty"`
|
||||
EnableDnsLog bool `protobuf:"varint,6,opt,name=enable_dns_log,json=enableDnsLog,proto3" json:"enable_dns_log,omitempty"`
|
||||
MaskAddress string `protobuf:"bytes,7,opt,name=mask_address,json=maskAddress,proto3" json:"mask_address,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_log_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_log_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -103,7 +102,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_log_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -160,13 +159,20 @@ func (x *Config) GetEnableDnsLog() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMaskAddress() string {
|
||||
if x != nil {
|
||||
return x.MaskAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_app_log_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_app_log_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x6c, 0x6f, 0x67, 0x1a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6c, 0x6f, 0x67,
|
||||
0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x02, 0x0a, 0x06, 0x43,
|
||||
0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xde, 0x02, 0x0a, 0x06, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c,
|
||||
0x6f, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x4c, 0x6f, 0x67,
|
||||
@@ -186,15 +192,18 @@ var file_app_log_config_proto_rawDesc = []byte{
|
||||
0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x50, 0x61,
|
||||
0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x6e, 0x73,
|
||||
0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x6e, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x44, 0x6e, 0x73, 0x4c, 0x6f, 0x67, 0x2a, 0x35, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x10, 0x03, 0x42,
|
||||
0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
||||
0x6c, 0x6f, 0x67, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6c, 0x65, 0x44, 0x6e, 0x73, 0x4c, 0x6f, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x73, 0x6b,
|
||||
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x6d, 0x61, 0x73, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2a, 0x35, 0x0a, 0x07, 0x4c,
|
||||
0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x46, 0x69, 0x6c, 0x65, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x10, 0x03, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
|
||||
0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
|
||||
0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0xaa, 0x02, 0x0c, 0x58, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -211,7 +220,7 @@ func file_app_log_config_proto_rawDescGZIP() []byte {
|
||||
|
||||
var file_app_log_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_app_log_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_app_log_config_proto_goTypes = []interface{}{
|
||||
var file_app_log_config_proto_goTypes = []any{
|
||||
(LogType)(0), // 0: xray.app.log.LogType
|
||||
(*Config)(nil), // 1: xray.app.log.Config
|
||||
(log.Severity)(0), // 2: xray.common.log.Severity
|
||||
@@ -232,20 +241,6 @@ func file_app_log_config_proto_init() {
|
||||
if File_app_log_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_log_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -23,4 +23,5 @@ message Config {
|
||||
LogType access_log_type = 4;
|
||||
string access_log_path = 5;
|
||||
bool enable_dns_log = 6;
|
||||
string mask_address= 7;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package log
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package log
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
)
|
||||
|
||||
@@ -29,13 +31,13 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
}
|
||||
log.RegisterHandler(g)
|
||||
|
||||
// start logger instantly on inited
|
||||
// other modules would log during init
|
||||
// start logger now,
|
||||
// then other modules will be able to log during initialization
|
||||
if err := g.startInternal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newError("Logger started").AtDebug().WriteToLog()
|
||||
errors.LogDebug(ctx, "Logger started")
|
||||
return g, nil
|
||||
}
|
||||
|
||||
@@ -77,10 +79,10 @@ func (g *Instance) startInternal() error {
|
||||
g.active = true
|
||||
|
||||
if err := g.initAccessLogger(); err != nil {
|
||||
return newError("failed to initialize access logger").Base(err).AtWarning()
|
||||
return errors.New("failed to initialize access logger").Base(err).AtWarning()
|
||||
}
|
||||
if err := g.initErrorLogger(); err != nil {
|
||||
return newError("failed to initialize error logger").Base(err).AtWarning()
|
||||
return errors.New("failed to initialize error logger").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -100,18 +102,25 @@ func (g *Instance) Handle(msg log.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
var Msg log.Message
|
||||
if g.config.MaskAddress != "" {
|
||||
Msg = &MaskedMsgWrapper{Message: msg, config: g.config}
|
||||
} else {
|
||||
Msg = msg
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *log.AccessMessage:
|
||||
if g.accessLogger != nil {
|
||||
g.accessLogger.Handle(msg)
|
||||
g.accessLogger.Handle(Msg)
|
||||
}
|
||||
case *log.DNSLog:
|
||||
if g.dns && g.accessLogger != nil {
|
||||
g.accessLogger.Handle(msg)
|
||||
g.accessLogger.Handle(Msg)
|
||||
}
|
||||
case *log.GeneralMessage:
|
||||
if g.errorLogger != nil && msg.Severity <= g.config.ErrorLogLevel {
|
||||
g.errorLogger.Handle(msg)
|
||||
g.errorLogger.Handle(Msg)
|
||||
}
|
||||
default:
|
||||
// Swallow
|
||||
@@ -120,7 +129,7 @@ func (g *Instance) Handle(msg log.Message) {
|
||||
|
||||
// Close implements common.Closable.Close().
|
||||
func (g *Instance) Close() error {
|
||||
newError("Logger closing").AtDebug().WriteToLog()
|
||||
errors.LogDebug(context.Background(), "Logger closing")
|
||||
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
@@ -140,6 +149,56 @@ func (g *Instance) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaskedMsgWrapper is to wrap the string() method to mask IP addresses in the log.
|
||||
type MaskedMsgWrapper struct {
|
||||
log.Message
|
||||
config *Config
|
||||
}
|
||||
|
||||
func (m *MaskedMsgWrapper) String() string {
|
||||
str := m.Message.String()
|
||||
|
||||
ipv4Regex := regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}`)
|
||||
ipv6Regex := regexp.MustCompile(`((?:[\da-fA-F]{0,4}:[\da-fA-F]{0,4}){2,7})(?:[\/\\%](\d{1,3}))?`)
|
||||
|
||||
// Process ipv4
|
||||
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(ip string) string {
|
||||
parts := strings.Split(ip, ".")
|
||||
switch m.config.MaskAddress {
|
||||
case "half":
|
||||
return fmt.Sprintf("%s.%s.*.*", parts[0], parts[1])
|
||||
case "quarter":
|
||||
return fmt.Sprintf("%s.*.*.*", parts[0])
|
||||
case "full":
|
||||
return "[Masked IPv4]"
|
||||
default:
|
||||
return ip
|
||||
}
|
||||
})
|
||||
|
||||
// process ipv6
|
||||
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(ip string) string {
|
||||
parts := strings.Split(ip, ":")
|
||||
switch m.config.MaskAddress {
|
||||
case "half":
|
||||
if len(parts) >= 2 {
|
||||
return fmt.Sprintf("%s:%s::/32", parts[0], parts[1])
|
||||
}
|
||||
case "quarter":
|
||||
if len(parts) >= 1 {
|
||||
return fmt.Sprintf("%s::/16", parts[0])
|
||||
}
|
||||
case "full":
|
||||
return "Masked IPv6" // Do not use [Masked IPv6] like ipv4, or you will get "[[Masked IPv6]]" (v6 address already has [])
|
||||
default:
|
||||
return ip
|
||||
}
|
||||
return ip
|
||||
})
|
||||
|
||||
return maskedMsg
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return New(ctx, config.(*Config))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ var handlerCreatorMapLock = &sync.RWMutex{}
|
||||
|
||||
func RegisterHandlerCreator(logType LogType, f HandlerCreator) error {
|
||||
if f == nil {
|
||||
return newError("nil HandlerCreator")
|
||||
return errors.New("nil HandlerCreator")
|
||||
}
|
||||
|
||||
handlerCreatorMapLock.Lock()
|
||||
@@ -35,7 +36,7 @@ func createHandler(logType LogType, options HandlerCreatorOptions) (log.Handler,
|
||||
|
||||
creator, found := handlerCreatorMap[logType]
|
||||
if !found {
|
||||
return nil, newError("unable to create log handler for ", logType)
|
||||
return nil, errors.New("unable to create log handler for ", logType)
|
||||
}
|
||||
return creator(logType, options)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/metrics/config.proto
|
||||
|
||||
package metrics
|
||||
@@ -27,16 +27,15 @@ type Config struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Tag of the outbound handler that handles metrics http connections.
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
Listen string `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_metrics_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_metrics_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -47,7 +46,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_metrics_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -69,20 +68,28 @@ func (x *Config) GetTag() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetListen() string {
|
||||
if x != nil {
|
||||
return x.Listen
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_app_metrics_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_app_metrics_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x1a, 0x0a, 0x06,
|
||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x32, 0x0a, 0x06,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
|
||||
0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70,
|
||||
0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74,
|
||||
0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e,
|
||||
0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
|
||||
0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74,
|
||||
0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -98,7 +105,7 @@ func file_app_metrics_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_metrics_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_app_metrics_config_proto_goTypes = []interface{}{
|
||||
var file_app_metrics_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.app.metrics.Config
|
||||
}
|
||||
var file_app_metrics_config_proto_depIdxs = []int32{
|
||||
@@ -114,20 +121,6 @@ func file_app_metrics_config_proto_init() {
|
||||
if File_app_metrics_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_metrics_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -10,4 +10,5 @@ option java_multiple_files = true;
|
||||
message Config {
|
||||
// Tag of the outbound handler that handles metrics http connections.
|
||||
string tag = 1;
|
||||
string listen = 2;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -23,12 +24,15 @@ type MetricsHandler struct {
|
||||
statsManager feature_stats.Manager
|
||||
observatory extension.Observatory
|
||||
tag string
|
||||
listen string
|
||||
tcpListener net.Listener
|
||||
}
|
||||
|
||||
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
||||
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
||||
c := &MetricsHandler{
|
||||
tag: config.Tag,
|
||||
tag: config.Tag,
|
||||
listen: config.Listen,
|
||||
}
|
||||
common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) {
|
||||
c.statsManager = sm
|
||||
@@ -86,6 +90,23 @@ func (p *MetricsHandler) Type() interface{} {
|
||||
}
|
||||
|
||||
func (p *MetricsHandler) Start() error {
|
||||
|
||||
// direct listen a port if listen is set
|
||||
if p.listen != "" {
|
||||
TCPlistener, err := net.Listen("tcp", p.listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tcpListener = TCPlistener
|
||||
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
listener := &OutboundListener{
|
||||
buffer: make(chan net.Conn, 4),
|
||||
done: done.New(),
|
||||
@@ -93,12 +114,12 @@ func (p *MetricsHandler) Start() error {
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
|
||||
newError("failed to start metrics server").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||
newError("failed to remove existing handler").WriteToLog()
|
||||
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
||||
}
|
||||
|
||||
return p.ohm.AddHandler(context.Background(), &Outbound{
|
||||
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
@@ -31,7 +33,7 @@ func (l *OutboundListener) add(conn net.Conn) {
|
||||
func (l *OutboundListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case <-l.done.Wait():
|
||||
return nil, newError("listen closed")
|
||||
return nil, errors.New("listen closed")
|
||||
case c := <-l.buffer:
|
||||
return c, nil
|
||||
}
|
||||
@@ -107,3 +109,13 @@ func (co *Outbound) Close() error {
|
||||
co.closed = true
|
||||
return co.listener.Close()
|
||||
}
|
||||
|
||||
// SenderSettings implements outbound.Handler.
|
||||
func (co *Outbound) SenderSettings() *serial.TypedMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxySettings implements outbound.Handler.
|
||||
func (co *Outbound) ProxySettings() *serial.TypedMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||
|
||||
const (
|
||||
rttFailed = time.Duration(math.MaxInt64 - iota)
|
||||
rttUntested
|
||||
|
||||
@@ -2,15 +2,18 @@ package burst
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/core"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/extension"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Observer struct {
|
||||
@@ -66,7 +69,7 @@ func (o *Observer) Start() error {
|
||||
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||
if !ok {
|
||||
|
||||
return nil, newError("outbound.Manager is not a HandlerSelector")
|
||||
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
||||
}
|
||||
|
||||
outbounds := hs.Select(o.config.SubjectSelector)
|
||||
@@ -86,13 +89,15 @@ func (o *Observer) Close() error {
|
||||
|
||||
func New(ctx context.Context, config *Config) (*Observer, error) {
|
||||
var outboundManager outbound.Manager
|
||||
err := core.RequireFeatures(ctx, func(om outbound.Manager) {
|
||||
var dispatcher routing.Dispatcher
|
||||
err := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {
|
||||
outboundManager = om
|
||||
dispatcher = rd
|
||||
})
|
||||
if err != nil {
|
||||
return nil, newError("Cannot get depended features").Base(err)
|
||||
return nil, errors.New("Cannot get depended features").Base(err)
|
||||
}
|
||||
hp := NewHealthPing(ctx, config.PingConfig)
|
||||
hp := NewHealthPing(ctx, dispatcher, config.PingConfig)
|
||||
return &Observer{
|
||||
config: config,
|
||||
ctx: ctx,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/observatory/burst/config.proto
|
||||
|
||||
package burst
|
||||
@@ -32,11 +32,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -47,7 +45,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -92,15 +90,15 @@ type HealthPingConfig struct {
|
||||
SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"`
|
||||
// ping timeout, int64 values of time.Duration
|
||||
Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
||||
// http method to make request
|
||||
HttpMethod string `protobuf:"bytes,6,opt,name=httpMethod,proto3" json:"httpMethod,omitempty"`
|
||||
}
|
||||
|
||||
func (x *HealthPingConfig) Reset() {
|
||||
*x = HealthPingConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HealthPingConfig) String() string {
|
||||
@@ -111,7 +109,7 @@ func (*HealthPingConfig) ProtoMessage() {}
|
||||
|
||||
func (x *HealthPingConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_burst_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -161,6 +159,13 @@ func (x *HealthPingConfig) GetTimeout() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *HealthPingConfig) GetHttpMethod() string {
|
||||
if x != nil {
|
||||
return x.HttpMethod
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_app_observatory_burst_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_app_observatory_burst_config_proto_rawDesc = []byte{
|
||||
@@ -177,7 +182,7 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{
|
||||
0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72,
|
||||
0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
|
||||
0xb4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f,
|
||||
0xd4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69,
|
||||
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
||||
@@ -188,7 +193,9 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{
|
||||
0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73,
|
||||
0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x65,
|
||||
0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70,
|
||||
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f,
|
||||
0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
||||
@@ -211,7 +218,7 @@ func file_app_observatory_burst_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_observatory_burst_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_app_observatory_burst_config_proto_goTypes = []interface{}{
|
||||
var file_app_observatory_burst_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.core.app.observatory.burst.Config
|
||||
(*HealthPingConfig)(nil), // 1: xray.core.app.observatory.burst.HealthPingConfig
|
||||
}
|
||||
@@ -229,32 +236,6 @@ func file_app_observatory_burst_config_proto_init() {
|
||||
if File_app_observatory_burst_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_observatory_burst_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_burst_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*HealthPingConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -26,4 +26,7 @@ message HealthPingConfig {
|
||||
int32 samplingCount = 4;
|
||||
// ping timeout, int64 values of time.Duration
|
||||
int64 timeout = 5;
|
||||
// http method to make request
|
||||
string httpMethod = 6;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package burst
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
// HealthPingSettings holds settings for health Checker
|
||||
@@ -17,11 +19,13 @@ type HealthPingSettings struct {
|
||||
Interval time.Duration `json:"interval"`
|
||||
SamplingCount int `json:"sampling"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
HttpMethod string `json:"httpMethod"`
|
||||
}
|
||||
|
||||
// HealthPing is the health checker for balancers
|
||||
type HealthPing struct {
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
tickerClose chan struct{}
|
||||
@@ -31,15 +35,24 @@ type HealthPing struct {
|
||||
}
|
||||
|
||||
// NewHealthPing creates a new HealthPing with settings
|
||||
func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing {
|
||||
func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *HealthPingConfig) *HealthPing {
|
||||
settings := &HealthPingSettings{}
|
||||
if config != nil {
|
||||
|
||||
var httpMethod string
|
||||
if config.HttpMethod == "" {
|
||||
httpMethod = "HEAD"
|
||||
} else {
|
||||
httpMethod = strings.TrimSpace(config.HttpMethod)
|
||||
}
|
||||
|
||||
settings = &HealthPingSettings{
|
||||
Connectivity: strings.TrimSpace(config.Connectivity),
|
||||
Destination: strings.TrimSpace(config.Destination),
|
||||
Interval: time.Duration(config.Interval),
|
||||
SamplingCount: int(config.SamplingCount),
|
||||
Timeout: time.Duration(config.Timeout),
|
||||
HttpMethod: httpMethod,
|
||||
}
|
||||
}
|
||||
if settings.Destination == "" {
|
||||
@@ -51,7 +64,7 @@ func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing {
|
||||
if settings.Interval == 0 {
|
||||
settings.Interval = time.Duration(1) * time.Minute
|
||||
} else if settings.Interval < 10 {
|
||||
newError("health check interval is too small, 10s is applied").AtWarning().WriteToLog()
|
||||
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
||||
settings.Interval = time.Duration(10) * time.Second
|
||||
}
|
||||
if settings.SamplingCount <= 0 {
|
||||
@@ -63,9 +76,10 @@ func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing {
|
||||
settings.Timeout = time.Duration(5) * time.Second
|
||||
}
|
||||
return &HealthPing{
|
||||
ctx: ctx,
|
||||
Settings: settings,
|
||||
Results: nil,
|
||||
ctx: ctx,
|
||||
dispatcher: dispatcher,
|
||||
Settings: settings,
|
||||
Results: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +96,7 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
newError("error select outbounds for initial health check: ", err).AtWarning().WriteToLog()
|
||||
errors.LogWarning(h.ctx, "error select outbounds for initial health check: ", err)
|
||||
return
|
||||
}
|
||||
h.Check(tags)
|
||||
@@ -93,7 +107,7 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
newError("error select outbounds for scheduled health check: ", err).AtWarning().WriteToLog()
|
||||
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||
return
|
||||
}
|
||||
h.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||
@@ -125,7 +139,7 @@ func (h *HealthPing) Check(tags []string) error {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
newError("perform one-time health check for tags ", tags).AtInfo().WriteToLog()
|
||||
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
||||
h.doCheck(tags, 0, 1)
|
||||
return nil
|
||||
}
|
||||
@@ -148,6 +162,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
handler := tag
|
||||
client := newPingClient(
|
||||
h.ctx,
|
||||
h.dispatcher,
|
||||
h.Settings.Destination,
|
||||
h.Settings.Timeout,
|
||||
handler,
|
||||
@@ -155,11 +170,11 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
for i := 0; i < rounds; i++ {
|
||||
delay := time.Duration(0)
|
||||
if duration > 0 {
|
||||
delay = time.Duration(dice.Roll(int(duration)))
|
||||
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||
}
|
||||
time.AfterFunc(delay, func() {
|
||||
newError("checking ", handler).AtDebug().WriteToLog()
|
||||
delay, err := client.MeasureDelay()
|
||||
errors.LogDebug(h.ctx, "checking ", handler)
|
||||
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||
if err == nil {
|
||||
ch <- &rtt{
|
||||
handler: handler,
|
||||
@@ -168,19 +183,19 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
return
|
||||
}
|
||||
if !h.checkConnectivity() {
|
||||
newError("network is down").AtWarning().WriteToLog()
|
||||
errors.LogWarning(h.ctx, "network is down")
|
||||
ch <- &rtt{
|
||||
handler: handler,
|
||||
value: 0,
|
||||
}
|
||||
return
|
||||
}
|
||||
newError(fmt.Sprintf(
|
||||
errors.LogWarning(h.ctx, fmt.Sprintf(
|
||||
"error ping %s with %s: %s",
|
||||
h.Settings.Destination,
|
||||
handler,
|
||||
err,
|
||||
)).AtWarning().WriteToLog()
|
||||
))
|
||||
ch <- &rtt{
|
||||
handler: handler,
|
||||
value: rttFailed,
|
||||
@@ -208,7 +223,7 @@ func (h *HealthPing) PutResult(tag string, rtt time.Duration) {
|
||||
if !ok {
|
||||
// validity is 2 times to sampling period, since the check are
|
||||
// distributed in the time line randomly, in extreme cases,
|
||||
// previous checks are distributed on the left, and latters
|
||||
// Previous checks are distributed on the left, and later ones
|
||||
// on the right
|
||||
validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2
|
||||
r = NewHealthPingResult(h.Settings.SamplingCount, validity)
|
||||
@@ -246,7 +261,7 @@ func (h *HealthPing) checkConnectivity() bool {
|
||||
h.Settings.Connectivity,
|
||||
h.Settings.Timeout,
|
||||
)
|
||||
if _, err := tester.MeasureDelay(); err != nil {
|
||||
if _, err := tester.MeasureDelay(h.Settings.HttpMethod); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -2,10 +2,12 @@ package burst
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||
)
|
||||
|
||||
@@ -14,10 +16,10 @@ type pingClient struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func newPingClient(ctx context.Context, destination string, timeout time.Duration, handler string) *pingClient {
|
||||
func newPingClient(ctx context.Context, dispatcher routing.Dispatcher, destination string, timeout time.Duration, handler string) *pingClient {
|
||||
return &pingClient{
|
||||
destination: destination,
|
||||
httpClient: newHTTPClient(ctx, handler, timeout),
|
||||
httpClient: newHTTPClient(ctx, dispatcher, handler, timeout),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +30,7 @@ func newDirectPingClient(destination string, timeout time.Duration) *pingClient
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPClient(ctxv context.Context, handler string, timeout time.Duration) *http.Client {
|
||||
func newHTTPClient(ctxv context.Context, dispatcher routing.Dispatcher, handler string, timeout time.Duration) *http.Client {
|
||||
tr := &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
@@ -36,7 +38,7 @@ func newHTTPClient(ctxv context.Context, handler string, timeout time.Duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tagged.Dialer(ctxv, dest, handler)
|
||||
return tagged.Dialer(ctxv, dispatcher, dest, handler)
|
||||
},
|
||||
}
|
||||
return &http.Client{
|
||||
@@ -50,20 +52,28 @@ func newHTTPClient(ctxv context.Context, handler string, timeout time.Duration)
|
||||
}
|
||||
|
||||
// MeasureDelay returns the delay time of the request to dest
|
||||
func (s *pingClient) MeasureDelay() (time.Duration, error) {
|
||||
func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
|
||||
if s.httpClient == nil {
|
||||
panic("pingClient no initialized")
|
||||
panic("pingClient not initialized")
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodHead, s.destination, nil)
|
||||
|
||||
req, err := http.NewRequest(httpMethod, s.destination, nil)
|
||||
if err != nil {
|
||||
return rttFailed, err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return rttFailed, err
|
||||
}
|
||||
// don't wait for body
|
||||
if httpMethod == http.MethodGet {
|
||||
_, err = io.Copy(io.Discard, resp.Body)
|
||||
if err != nil {
|
||||
return rttFailed, err
|
||||
}
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
return time.Since(start), nil
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func init() {
|
||||
sv := &service{v: s}
|
||||
err := s.RequireFeatures(func(Observatory extension.Observatory) {
|
||||
sv.observatory = Observatory
|
||||
})
|
||||
}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/observatory/command/command.proto
|
||||
|
||||
package command
|
||||
@@ -29,11 +29,9 @@ type GetOutboundStatusRequest struct {
|
||||
|
||||
func (x *GetOutboundStatusRequest) Reset() {
|
||||
*x = GetOutboundStatusRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetOutboundStatusRequest) String() string {
|
||||
@@ -44,7 +42,7 @@ func (*GetOutboundStatusRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetOutboundStatusRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -69,11 +67,9 @@ type GetOutboundStatusResponse struct {
|
||||
|
||||
func (x *GetOutboundStatusResponse) Reset() {
|
||||
*x = GetOutboundStatusResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetOutboundStatusResponse) String() string {
|
||||
@@ -84,7 +80,7 @@ func (*GetOutboundStatusResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetOutboundStatusResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -114,11 +110,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -129,7 +123,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_command_command_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -197,7 +191,7 @@ func file_app_observatory_command_command_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_observatory_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_app_observatory_command_command_proto_goTypes = []interface{}{
|
||||
var file_app_observatory_command_command_proto_goTypes = []any{
|
||||
(*GetOutboundStatusRequest)(nil), // 0: xray.core.app.observatory.command.GetOutboundStatusRequest
|
||||
(*GetOutboundStatusResponse)(nil), // 1: xray.core.app.observatory.command.GetOutboundStatusResponse
|
||||
(*Config)(nil), // 2: xray.core.app.observatory.command.Config
|
||||
@@ -219,44 +213,6 @@ func file_app_observatory_command_command_proto_init() {
|
||||
if File_app_observatory_command_command_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_observatory_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetOutboundStatusRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetOutboundStatusResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v5.27.0
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.28.2
|
||||
// source: app/observatory/command/command.proto
|
||||
|
||||
package command
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
ObservatoryService_GetOutboundStatus_FullMethodName = "/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus"
|
||||
@@ -38,8 +38,9 @@ func NewObservatoryServiceClient(cc grpc.ClientConnInterface) ObservatoryService
|
||||
}
|
||||
|
||||
func (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetOutboundStatusResponse)
|
||||
err := c.cc.Invoke(ctx, ObservatoryService_GetOutboundStatus_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, ObservatoryService_GetOutboundStatus_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,20 +49,24 @@ func (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *Ge
|
||||
|
||||
// ObservatoryServiceServer is the server API for ObservatoryService service.
|
||||
// All implementations must embed UnimplementedObservatoryServiceServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
type ObservatoryServiceServer interface {
|
||||
GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error)
|
||||
mustEmbedUnimplementedObservatoryServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedObservatoryServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedObservatoryServiceServer struct {
|
||||
}
|
||||
// UnimplementedObservatoryServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedObservatoryServiceServer struct{}
|
||||
|
||||
func (UnimplementedObservatoryServiceServer) GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetOutboundStatus not implemented")
|
||||
}
|
||||
func (UnimplementedObservatoryServiceServer) mustEmbedUnimplementedObservatoryServiceServer() {}
|
||||
func (UnimplementedObservatoryServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeObservatoryServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ObservatoryServiceServer will
|
||||
@@ -71,6 +76,13 @@ type UnsafeObservatoryServiceServer interface {
|
||||
}
|
||||
|
||||
func RegisterObservatoryServiceServer(s grpc.ServiceRegistrar, srv ObservatoryServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedObservatoryServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&ObservatoryService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/observatory/config.proto
|
||||
|
||||
package observatory
|
||||
@@ -30,11 +30,9 @@ type ObservationResult struct {
|
||||
|
||||
func (x *ObservationResult) Reset() {
|
||||
*x = ObservationResult{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ObservationResult) String() string {
|
||||
@@ -45,7 +43,7 @@ func (*ObservationResult) ProtoMessage() {}
|
||||
|
||||
func (x *ObservationResult) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -82,11 +80,9 @@ type HealthPingMeasurementResult struct {
|
||||
|
||||
func (x *HealthPingMeasurementResult) Reset() {
|
||||
*x = HealthPingMeasurementResult{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HealthPingMeasurementResult) String() string {
|
||||
@@ -97,7 +93,7 @@ func (*HealthPingMeasurementResult) ProtoMessage() {}
|
||||
|
||||
func (x *HealthPingMeasurementResult) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -183,11 +179,9 @@ type OutboundStatus struct {
|
||||
|
||||
func (x *OutboundStatus) Reset() {
|
||||
*x = OutboundStatus{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *OutboundStatus) String() string {
|
||||
@@ -198,7 +192,7 @@ func (*OutboundStatus) ProtoMessage() {}
|
||||
|
||||
func (x *OutboundStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -281,11 +275,9 @@ type ProbeResult struct {
|
||||
|
||||
func (x *ProbeResult) Reset() {
|
||||
*x = ProbeResult{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProbeResult) String() string {
|
||||
@@ -296,7 +288,7 @@ func (*ProbeResult) ProtoMessage() {}
|
||||
|
||||
func (x *ProbeResult) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -344,11 +336,9 @@ type Intensity struct {
|
||||
|
||||
func (x *Intensity) Reset() {
|
||||
*x = Intensity{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Intensity) String() string {
|
||||
@@ -359,7 +349,7 @@ func (*Intensity) ProtoMessage() {}
|
||||
|
||||
func (x *Intensity) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -395,11 +385,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_observatory_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -410,7 +398,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_observatory_config_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -536,7 +524,7 @@ func file_app_observatory_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_app_observatory_config_proto_goTypes = []interface{}{
|
||||
var file_app_observatory_config_proto_goTypes = []any{
|
||||
(*ObservationResult)(nil), // 0: xray.core.app.observatory.ObservationResult
|
||||
(*HealthPingMeasurementResult)(nil), // 1: xray.core.app.observatory.HealthPingMeasurementResult
|
||||
(*OutboundStatus)(nil), // 2: xray.core.app.observatory.OutboundStatus
|
||||
@@ -559,80 +547,6 @@ func file_app_observatory_config_proto_init() {
|
||||
if File_app_observatory_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_observatory_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ObservationResult); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*HealthPingMeasurementResult); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OutboundStatus); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ProbeResult); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Intensity); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_observatory_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package observatory
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -8,10 +8,10 @@ type errorCollector struct {
|
||||
|
||||
func (e *errorCollector) SubmitError(err error) {
|
||||
if e.errors == nil {
|
||||
e.errors = newError("underlying connection error").Base(err)
|
||||
e.errors = errors.New("underlying connection error").Base(err)
|
||||
return
|
||||
}
|
||||
e.errors = e.errors.Base(newError("underlying connection error").Base(err))
|
||||
e.errors = e.errors.Base(errors.New("underlying connection error").Base(err))
|
||||
}
|
||||
|
||||
func newErrorCollector() *errorCollector {
|
||||
@@ -20,7 +20,7 @@ func newErrorCollector() *errorCollector {
|
||||
|
||||
func (e *errorCollector) UnderlyingError() error {
|
||||
if e.errors == nil {
|
||||
return newError("failed to produce report")
|
||||
return errors.New("failed to produce report")
|
||||
}
|
||||
return e.errors
|
||||
}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
package observatory
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
v2net "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/extension"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -30,7 +32,8 @@ type Observer struct {
|
||||
|
||||
finished *done.Instance
|
||||
|
||||
ohm outbound.Manager
|
||||
ohm outbound.Manager
|
||||
dispatcher routing.Dispatcher
|
||||
}
|
||||
|
||||
func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {
|
||||
@@ -60,7 +63,7 @@ func (o *Observer) background() {
|
||||
for !o.finished.Done() {
|
||||
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||
if !ok {
|
||||
newError("outbound.Manager is not a HandlerSelector").WriteToLog()
|
||||
errors.LogInfo(o.ctx, "outbound.Manager is not a HandlerSelector")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,18 +130,18 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
// MUST use Xray's built in context system
|
||||
dest, err := v2net.ParseDestination(network + ":" + addr)
|
||||
if err != nil {
|
||||
return newError("cannot understand address").Base(err)
|
||||
return errors.New("cannot understand address").Base(err)
|
||||
}
|
||||
trackedCtx := session.TrackedConnectionError(o.ctx, errorCollectorForRequest)
|
||||
conn, err := tagged.Dialer(trackedCtx, dest, outbound)
|
||||
conn, err := tagged.Dialer(trackedCtx, o.dispatcher, dest, outbound)
|
||||
if err != nil {
|
||||
return newError("cannot dial remote address ", dest).Base(err)
|
||||
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||
}
|
||||
connection = conn
|
||||
return nil
|
||||
})
|
||||
if taskErr != nil {
|
||||
return nil, newError("cannot finish connection").Base(taskErr)
|
||||
return nil, errors.New("cannot finish connection").Base(taskErr)
|
||||
}
|
||||
return connection, nil
|
||||
},
|
||||
@@ -161,7 +164,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
}
|
||||
response, err := httpClient.Get(probeURL)
|
||||
if err != nil {
|
||||
return newError("outbound failed to relay connection").Base(err)
|
||||
return errors.New("outbound failed to relay connection").Base(err)
|
||||
}
|
||||
if response.Body != nil {
|
||||
response.Body.Close()
|
||||
@@ -171,15 +174,11 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fullerr := newError("underlying connection failed").Base(errorCollectorForRequest.UnderlyingError())
|
||||
fullerr = newError("with outbound handler report").Base(fullerr)
|
||||
fullerr = newError("GET request failed:", err).Base(fullerr)
|
||||
fullerr = newError("the outbound ", outbound, " is dead:").Base(fullerr)
|
||||
fullerr = fullerr.AtInfo()
|
||||
fullerr.WriteToLog()
|
||||
return ProbeResult{Alive: false, LastErrorReason: fullerr.Error()}
|
||||
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
|
||||
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
||||
}
|
||||
newError("the outbound ", outbound, " is alive:", GETTime.Seconds()).AtInfo().WriteToLog()
|
||||
errors.LogInfo(o.ctx, "the outbound ", outbound, " is alive:", GETTime.Seconds())
|
||||
return ProbeResult{Alive: true, Delay: GETTime.Milliseconds()}
|
||||
}
|
||||
|
||||
@@ -218,16 +217,19 @@ func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int {
|
||||
|
||||
func New(ctx context.Context, config *Config) (*Observer, error) {
|
||||
var outboundManager outbound.Manager
|
||||
err := core.RequireFeatures(ctx, func(om outbound.Manager) {
|
||||
var dispatcher routing.Dispatcher
|
||||
err := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {
|
||||
outboundManager = om
|
||||
dispatcher = rd
|
||||
})
|
||||
if err != nil {
|
||||
return nil, newError("Cannot get depended features").Base(err)
|
||||
return nil, errors.New("Cannot get depended features").Base(err)
|
||||
}
|
||||
return &Observer{
|
||||
config: config,
|
||||
ctx: ctx,
|
||||
ohm: outboundManager,
|
||||
config: config,
|
||||
ctx: ctx,
|
||||
ohm: outboundManager,
|
||||
dispatcher: dispatcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ func (p *Policy) ToCorePolicy() policy.Session {
|
||||
if p.Stats != nil {
|
||||
cp.Stats.UserUplink = p.Stats.UserUplink
|
||||
cp.Stats.UserDownlink = p.Stats.UserDownlink
|
||||
cp.Stats.UserOnline = p.Stats.UserOnline
|
||||
}
|
||||
if p.Buffer != nil {
|
||||
cp.Buffer.PerConnection = p.Buffer.Connection
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.27.0
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// source: app/policy/config.proto
|
||||
|
||||
package policy
|
||||
@@ -30,11 +30,9 @@ type Second struct {
|
||||
|
||||
func (x *Second) Reset() {
|
||||
*x = Second{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Second) String() string {
|
||||
@@ -45,7 +43,7 @@ func (*Second) ProtoMessage() {}
|
||||
|
||||
func (x *Second) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -79,11 +77,9 @@ type Policy struct {
|
||||
|
||||
func (x *Policy) Reset() {
|
||||
*x = Policy{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Policy) String() string {
|
||||
@@ -94,7 +90,7 @@ func (*Policy) ProtoMessage() {}
|
||||
|
||||
func (x *Policy) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -140,11 +136,9 @@ type SystemPolicy struct {
|
||||
|
||||
func (x *SystemPolicy) Reset() {
|
||||
*x = SystemPolicy{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SystemPolicy) String() string {
|
||||
@@ -155,7 +149,7 @@ func (*SystemPolicy) ProtoMessage() {}
|
||||
|
||||
func (x *SystemPolicy) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -188,11 +182,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -203,7 +195,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -246,11 +238,9 @@ type Policy_Timeout struct {
|
||||
|
||||
func (x *Policy_Timeout) Reset() {
|
||||
*x = Policy_Timeout{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Policy_Timeout) String() string {
|
||||
@@ -261,7 +251,7 @@ func (*Policy_Timeout) ProtoMessage() {}
|
||||
|
||||
func (x *Policy_Timeout) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -311,15 +301,14 @@ type Policy_Stats struct {
|
||||
|
||||
UserUplink bool `protobuf:"varint,1,opt,name=user_uplink,json=userUplink,proto3" json:"user_uplink,omitempty"`
|
||||
UserDownlink bool `protobuf:"varint,2,opt,name=user_downlink,json=userDownlink,proto3" json:"user_downlink,omitempty"`
|
||||
UserOnline bool `protobuf:"varint,3,opt,name=user_online,json=userOnline,proto3" json:"user_online,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Policy_Stats) Reset() {
|
||||
*x = Policy_Stats{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Policy_Stats) String() string {
|
||||
@@ -330,7 +319,7 @@ func (*Policy_Stats) ProtoMessage() {}
|
||||
|
||||
func (x *Policy_Stats) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -359,6 +348,13 @@ func (x *Policy_Stats) GetUserDownlink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Policy_Stats) GetUserOnline() bool {
|
||||
if x != nil {
|
||||
return x.UserOnline
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Policy_Buffer struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -370,11 +366,9 @@ type Policy_Buffer struct {
|
||||
|
||||
func (x *Policy_Buffer) Reset() {
|
||||
*x = Policy_Buffer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Policy_Buffer) String() string {
|
||||
@@ -385,7 +379,7 @@ func (*Policy_Buffer) ProtoMessage() {}
|
||||
|
||||
func (x *Policy_Buffer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -420,11 +414,9 @@ type SystemPolicy_Stats struct {
|
||||
|
||||
func (x *SystemPolicy_Stats) Reset() {
|
||||
*x = SystemPolicy_Stats{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_app_policy_config_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_app_policy_config_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SystemPolicy_Stats) String() string {
|
||||
@@ -435,7 +427,7 @@ func (*SystemPolicy_Stats) ProtoMessage() {}
|
||||
|
||||
func (x *SystemPolicy_Stats) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_policy_config_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -485,7 +477,7 @@ var file_app_policy_config_proto_rawDesc = []byte{
|
||||
0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x1e, 0x0a, 0x06, 0x53, 0x65,
|
||||
0x63, 0x6f, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xa6, 0x04, 0x0a, 0x06, 0x50,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc7, 0x04, 0x0a, 0x06, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e,
|
||||
@@ -512,49 +504,51 @@ var file_app_policy_config_proto_rawDesc = []byte{
|
||||
0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x0c, 0x64, 0x6f,
|
||||
0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x1a, 0x4d, 0x0a, 0x05, 0x53, 0x74,
|
||||
0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x1a, 0x6e, 0x0a, 0x05, 0x53, 0x74,
|
||||
0x61, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x6c, 0x69,
|
||||
0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x55, 0x70,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6f, 0x77,
|
||||
0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65,
|
||||
0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x1a, 0x28, 0x0a, 0x06, 0x42, 0x75, 0x66,
|
||||
0x66, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x22, 0xfb, 0x01, 0x0a, 0x0c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f,
|
||||
0x6c, 0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a,
|
||||
0xaf, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0d, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b,
|
||||
0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x77, 0x6e,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x62, 0x6f,
|
||||
0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x6f,
|
||||
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e,
|
||||
0x6b, 0x22, 0xcc, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x05,
|
||||
0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
|
||||
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x1a, 0x51, 0x0a,
|
||||
0x0a, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
|
||||
0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d,
|
||||
0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xaa,
|
||||
0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
|
||||
0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65,
|
||||
0x72, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
|
||||
0x75, 0x73, 0x65, 0x72, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x1a, 0x28, 0x0a, 0x06, 0x42, 0x75,
|
||||
0x66, 0x66, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfb, 0x01, 0x0a, 0x0c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
||||
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c,
|
||||
0x69, 0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73,
|
||||
0x1a, 0xaf, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, 0x6c, 0x69, 0x6e,
|
||||
0x6b, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x77,
|
||||
0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f,
|
||||
0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55,
|
||||
0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69,
|
||||
0x6e, 0x6b, 0x22, 0xcc, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a,
|
||||
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65,
|
||||
0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
|
||||
0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x1a, 0x51,
|
||||
0x0a, 0x0a, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d,
|
||||
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
|
||||
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
|
||||
0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -570,7 +564,7 @@ func file_app_policy_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_app_policy_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_app_policy_config_proto_goTypes = []interface{}{
|
||||
var file_app_policy_config_proto_goTypes = []any{
|
||||
(*Second)(nil), // 0: xray.app.policy.Second
|
||||
(*Policy)(nil), // 1: xray.app.policy.Policy
|
||||
(*SystemPolicy)(nil), // 2: xray.app.policy.SystemPolicy
|
||||
@@ -605,104 +599,6 @@ func file_app_policy_config_proto_init() {
|
||||
if File_app_policy_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_app_policy_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Second); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Policy); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SystemPolicy); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Policy_Timeout); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Policy_Stats); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Policy_Buffer); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_app_policy_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SystemPolicy_Stats); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -22,6 +22,7 @@ message Policy {
|
||||
message Stats {
|
||||
bool user_uplink = 1;
|
||||
bool user_downlink = 2;
|
||||
bool user_online = 3;
|
||||
}
|
||||
|
||||
message Buffer {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package policy
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
// Package policy is an implementation of policy.Manager feature.
|
||||
package policy
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
@@ -3,7 +3,10 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/app/commander"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/inbound"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
@@ -26,7 +29,7 @@ type OutboundOperation interface {
|
||||
func getInbound(handler inbound.Handler) (proxy.Inbound, error) {
|
||||
gi, ok := handler.(proxy.GetInbound)
|
||||
if !ok {
|
||||
return nil, newError("can't get inbound proxy from handler.")
|
||||
return nil, errors.New("can't get inbound proxy from handler.")
|
||||
}
|
||||
return gi.GetInbound(), nil
|
||||
}
|
||||
@@ -39,11 +42,11 @@ func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Ha
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return newError("proxy is not a UserManager")
|
||||
return errors.New("proxy is not a UserManager")
|
||||
}
|
||||
mUser, err := op.User.ToMemoryUser()
|
||||
if err != nil {
|
||||
return newError("failed to parse user").Base(err)
|
||||
return errors.New("failed to parse user").Base(err)
|
||||
}
|
||||
return um.AddUser(ctx, mUser)
|
||||
}
|
||||
@@ -56,7 +59,7 @@ func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return newError("proxy is not a UserManager")
|
||||
return errors.New("proxy is not a UserManager")
|
||||
}
|
||||
return um.RemoveUser(ctx, op.Email)
|
||||
}
|
||||
@@ -82,21 +85,83 @@ func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboun
|
||||
func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) {
|
||||
rawOperation, err := request.Operation.GetInstance()
|
||||
if err != nil {
|
||||
return nil, newError("unknown operation").Base(err)
|
||||
return nil, errors.New("unknown operation").Base(err)
|
||||
}
|
||||
operation, ok := rawOperation.(InboundOperation)
|
||||
if !ok {
|
||||
return nil, newError("not an inbound operation")
|
||||
return nil, errors.New("not an inbound operation")
|
||||
}
|
||||
|
||||
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||
if err != nil {
|
||||
return nil, newError("failed to get handler: ", request.Tag).Base(err)
|
||||
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||
}
|
||||
|
||||
return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)
|
||||
}
|
||||
|
||||
func (s *handlerServer) ListInbounds(ctx context.Context, request *ListInboundsRequest) (*ListInboundsResponse, error) {
|
||||
handlers := s.ihm.ListHandlers(ctx)
|
||||
response := &ListInboundsResponse{}
|
||||
if request.GetIsOnlyTags() {
|
||||
for _, handler := range handlers {
|
||||
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
|
||||
Tag: handler.Tag(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, handler := range handlers {
|
||||
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
|
||||
Tag: handler.Tag(),
|
||||
ReceiverSettings: handler.ReceiverSettings(),
|
||||
ProxySettings: handler.ProxySettings(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUserResponse, error) {
|
||||
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||
}
|
||||
p, err := getInbound(handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return nil, errors.New("proxy is not a UserManager")
|
||||
}
|
||||
if len(request.Email) > 0 {
|
||||
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
|
||||
}
|
||||
var result = make([]*protocol.User, 0, 100)
|
||||
users := um.GetUsers(ctx)
|
||||
for _, u := range users {
|
||||
result = append(result, protocol.ToProtoUser(u))
|
||||
}
|
||||
return &GetInboundUserResponse{Users: result}, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) GetInboundUsersCount(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) {
|
||||
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||
}
|
||||
p, err := getInbound(handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
um, ok := p.(proxy.UserManager)
|
||||
if !ok {
|
||||
return nil, errors.New("proxy is not a UserManager")
|
||||
}
|
||||
return &GetInboundUsersCountResponse{Count: um.GetUsersCount(ctx)}, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) {
|
||||
if err := core.AddOutboundHandler(s.s, request.Outbound); err != nil {
|
||||
return nil, err
|
||||
@@ -111,17 +176,34 @@ func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutbo
|
||||
func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) {
|
||||
rawOperation, err := request.Operation.GetInstance()
|
||||
if err != nil {
|
||||
return nil, newError("unknown operation").Base(err)
|
||||
return nil, errors.New("unknown operation").Base(err)
|
||||
}
|
||||
operation, ok := rawOperation.(OutboundOperation)
|
||||
if !ok {
|
||||
return nil, newError("not an outbound operation")
|
||||
return nil, errors.New("not an outbound operation")
|
||||
}
|
||||
|
||||
handler := s.ohm.GetHandler(request.Tag)
|
||||
return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)
|
||||
}
|
||||
|
||||
func (s *handlerServer) ListOutbounds(ctx context.Context, request *ListOutboundsRequest) (*ListOutboundsResponse, error) {
|
||||
handlers := s.ohm.ListHandlers(ctx)
|
||||
response := &ListOutboundsResponse{}
|
||||
for _, handler := range handlers {
|
||||
// Ignore gRPC outbound
|
||||
if _, ok := handler.(*commander.Outbound); ok {
|
||||
continue
|
||||
}
|
||||
response.Outbounds = append(response.Outbounds, &core.OutboundHandlerConfig{
|
||||
Tag: handler.Tag(),
|
||||
SenderSettings: handler.SenderSettings(),
|
||||
ProxySettings: handler.ProxySettings(),
|
||||
})
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {}
|
||||
|
||||
type service struct {
|
||||
@@ -135,7 +217,7 @@ func (s *service) Register(server *grpc.Server) {
|
||||
common.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {
|
||||
hs.ihm = im
|
||||
hs.ohm = om
|
||||
}))
|
||||
}, false))
|
||||
RegisterHandlerServiceServer(server, hs)
|
||||
|
||||
// For compatibility purposes
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,27 @@ message AlterInboundRequest {
|
||||
|
||||
message AlterInboundResponse {}
|
||||
|
||||
message ListInboundsRequest {
|
||||
bool isOnlyTags = 1;
|
||||
}
|
||||
|
||||
message ListInboundsResponse {
|
||||
repeated core.InboundHandlerConfig inbounds = 1;
|
||||
}
|
||||
|
||||
message GetInboundUserRequest {
|
||||
string tag = 1;
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
message GetInboundUserResponse {
|
||||
repeated xray.common.protocol.User users = 1;
|
||||
}
|
||||
|
||||
message GetInboundUsersCountResponse {
|
||||
int64 count = 1;
|
||||
}
|
||||
|
||||
message AddOutboundRequest {
|
||||
core.OutboundHandlerConfig outbound = 1;
|
||||
}
|
||||
@@ -56,6 +77,12 @@ message AlterOutboundRequest {
|
||||
|
||||
message AlterOutboundResponse {}
|
||||
|
||||
message ListOutboundsRequest {}
|
||||
|
||||
message ListOutboundsResponse {
|
||||
repeated core.OutboundHandlerConfig outbounds = 1;
|
||||
}
|
||||
|
||||
service HandlerService {
|
||||
rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}
|
||||
|
||||
@@ -63,11 +90,19 @@ service HandlerService {
|
||||
|
||||
rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}
|
||||
|
||||
rpc ListInbounds(ListInboundsRequest) returns (ListInboundsResponse) {}
|
||||
|
||||
rpc GetInboundUsers(GetInboundUserRequest) returns (GetInboundUserResponse) {}
|
||||
|
||||
rpc GetInboundUsersCount(GetInboundUserRequest) returns (GetInboundUsersCountResponse) {}
|
||||
|
||||
rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {}
|
||||
|
||||
rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}
|
||||
|
||||
rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}
|
||||
|
||||
rpc ListOutbounds(ListOutboundsRequest) returns (ListOutboundsResponse) {}
|
||||
}
|
||||
|
||||
message Config {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v5.27.0
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.28.2
|
||||
// source: app/proxyman/command/command.proto
|
||||
|
||||
package command
|
||||
@@ -15,16 +15,20 @@ import (
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
HandlerService_AddInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddInbound"
|
||||
HandlerService_RemoveInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveInbound"
|
||||
HandlerService_AlterInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterInbound"
|
||||
HandlerService_AddOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddOutbound"
|
||||
HandlerService_RemoveOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveOutbound"
|
||||
HandlerService_AlterOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterOutbound"
|
||||
HandlerService_AddInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddInbound"
|
||||
HandlerService_RemoveInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveInbound"
|
||||
HandlerService_AlterInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterInbound"
|
||||
HandlerService_ListInbounds_FullMethodName = "/xray.app.proxyman.command.HandlerService/ListInbounds"
|
||||
HandlerService_GetInboundUsers_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsers"
|
||||
HandlerService_GetInboundUsersCount_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsersCount"
|
||||
HandlerService_AddOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddOutbound"
|
||||
HandlerService_RemoveOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveOutbound"
|
||||
HandlerService_AlterOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterOutbound"
|
||||
HandlerService_ListOutbounds_FullMethodName = "/xray.app.proxyman.command.HandlerService/ListOutbounds"
|
||||
)
|
||||
|
||||
// HandlerServiceClient is the client API for HandlerService service.
|
||||
@@ -34,9 +38,13 @@ type HandlerServiceClient interface {
|
||||
AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error)
|
||||
RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error)
|
||||
AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error)
|
||||
ListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error)
|
||||
GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error)
|
||||
GetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error)
|
||||
AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error)
|
||||
RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error)
|
||||
AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error)
|
||||
ListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error)
|
||||
}
|
||||
|
||||
type handlerServiceClient struct {
|
||||
@@ -48,8 +56,9 @@ func NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient {
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AddInboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AddInbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AddInbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -57,8 +66,9 @@ func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundReq
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RemoveInboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_RemoveInbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_RemoveInbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,8 +76,39 @@ func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInbo
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AlterInboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AlterInbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AlterInbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) ListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListInboundsResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_ListInbounds_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetInboundUserResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_GetInboundUsers_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) GetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetInboundUsersCountResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_GetInboundUsersCount_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,8 +116,9 @@ func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboun
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AddOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AddOutbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AddOutbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,8 +126,9 @@ func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundR
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RemoveOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_RemoveOutbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_RemoveOutbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,8 +136,19 @@ func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOut
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AlterOutboundResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AlterOutbound_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, HandlerService_AlterOutbound_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *handlerServiceClient) ListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListOutboundsResponse)
|
||||
err := c.cc.Invoke(ctx, HandlerService_ListOutbounds_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,20 +157,27 @@ func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutbo
|
||||
|
||||
// HandlerServiceServer is the server API for HandlerService service.
|
||||
// All implementations must embed UnimplementedHandlerServiceServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
type HandlerServiceServer interface {
|
||||
AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error)
|
||||
RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error)
|
||||
AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error)
|
||||
ListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error)
|
||||
GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error)
|
||||
GetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error)
|
||||
AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error)
|
||||
RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error)
|
||||
AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error)
|
||||
ListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error)
|
||||
mustEmbedUnimplementedHandlerServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedHandlerServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedHandlerServiceServer struct {
|
||||
}
|
||||
// UnimplementedHandlerServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedHandlerServiceServer struct{}
|
||||
|
||||
func (UnimplementedHandlerServiceServer) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddInbound not implemented")
|
||||
@@ -127,6 +188,15 @@ func (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveI
|
||||
func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) ListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListInbounds not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetInboundUsers not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) GetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetInboundUsersCount not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddOutbound not implemented")
|
||||
}
|
||||
@@ -136,7 +206,11 @@ func (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *Remove
|
||||
func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) ListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListOutbounds not implemented")
|
||||
}
|
||||
func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {}
|
||||
func (UnimplementedHandlerServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeHandlerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to HandlerServiceServer will
|
||||
@@ -146,6 +220,13 @@ type UnsafeHandlerServiceServer interface {
|
||||
}
|
||||
|
||||
func RegisterHandlerServiceServer(s grpc.ServiceRegistrar, srv HandlerServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedHandlerServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&HandlerService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
@@ -203,6 +284,60 @@ func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_ListInbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListInboundsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).ListInbounds(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HandlerService_ListInbounds_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).ListInbounds(ctx, req.(*ListInboundsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_GetInboundUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetInboundUserRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).GetInboundUsers(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HandlerService_GetInboundUsers_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).GetInboundUsers(ctx, req.(*GetInboundUserRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_GetInboundUsersCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetInboundUserRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).GetInboundUsersCount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HandlerService_GetInboundUsersCount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).GetInboundUsersCount(ctx, req.(*GetInboundUserRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddOutboundRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -257,6 +392,24 @@ func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _HandlerService_ListOutbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListOutboundsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandlerServiceServer).ListOutbounds(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HandlerService_ListOutbounds_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandlerServiceServer).ListOutbounds(ctx, req.(*ListOutboundsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// HandlerService_ServiceDesc is the grpc.ServiceDesc for HandlerService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -276,6 +429,18 @@ var HandlerService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "AlterInbound",
|
||||
Handler: _HandlerService_AlterInbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListInbounds",
|
||||
Handler: _HandlerService_ListInbounds_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetInboundUsers",
|
||||
Handler: _HandlerService_GetInboundUsers_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetInboundUsersCount",
|
||||
Handler: _HandlerService_GetInboundUsersCount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AddOutbound",
|
||||
Handler: _HandlerService_AddOutbound_Handler,
|
||||
@@ -288,6 +453,10 @@ var HandlerService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "AlterOutbound",
|
||||
Handler: _HandlerService_AlterOutbound_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListOutbounds",
|
||||
Handler: _HandlerService_ListOutbounds_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "app/proxyman/command/command.proto",
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
package command
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package command
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,39 +1 @@
|
||||
package proxyman
|
||||
|
||||
func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
|
||||
if s == nil || s.Concurrency == nil {
|
||||
return 3
|
||||
}
|
||||
return s.Concurrency.Value
|
||||
}
|
||||
|
||||
func (s *AllocationStrategy) GetRefreshValue() uint32 {
|
||||
if s == nil || s.Refresh == nil {
|
||||
return 5
|
||||
}
|
||||
return s.Refresh.Value
|
||||
}
|
||||
|
||||
func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig {
|
||||
if c.SniffingSettings != nil {
|
||||
return c.SniffingSettings
|
||||
}
|
||||
|
||||
if len(c.DomainOverride) > 0 {
|
||||
var p []string
|
||||
for _, kd := range c.DomainOverride {
|
||||
switch kd {
|
||||
case KnownProtocols_HTTP:
|
||||
p = append(p, "http")
|
||||
case KnownProtocols_TLS:
|
||||
p = append(p, "tls")
|
||||
}
|
||||
}
|
||||
return &SniffingConfig{
|
||||
Enabled: true,
|
||||
DestinationOverride: p,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,38 +13,6 @@ import "common/serial/typed_message.proto";
|
||||
|
||||
message InboundConfig {}
|
||||
|
||||
message AllocationStrategy {
|
||||
enum Type {
|
||||
// Always allocate all connection handlers.
|
||||
Always = 0;
|
||||
|
||||
// Randomly allocate specific range of handlers.
|
||||
Random = 1;
|
||||
|
||||
// External. Not supported yet.
|
||||
External = 2;
|
||||
}
|
||||
|
||||
Type type = 1;
|
||||
|
||||
message AllocationStrategyConcurrency { uint32 value = 1; }
|
||||
|
||||
// Number of handlers (ports) running in parallel.
|
||||
// Default value is 3 if unset.
|
||||
AllocationStrategyConcurrency concurrency = 2;
|
||||
|
||||
message AllocationStrategyRefresh { uint32 value = 1; }
|
||||
|
||||
// Number of minutes before a handler is regenerated.
|
||||
// Default value is 5 if unset.
|
||||
AllocationStrategyRefresh refresh = 3;
|
||||
}
|
||||
|
||||
enum KnownProtocols {
|
||||
HTTP = 0;
|
||||
TLS = 1;
|
||||
}
|
||||
|
||||
message SniffingConfig {
|
||||
// Whether or not to enable content sniffing on an inbound connection.
|
||||
bool enabled = 1;
|
||||
@@ -67,14 +35,10 @@ message ReceiverConfig {
|
||||
xray.common.net.PortList port_list = 1;
|
||||
// Listen specifies the IP address that the Receiver should listen on.
|
||||
xray.common.net.IPOrDomain listen = 2;
|
||||
AllocationStrategy allocation_strategy = 3;
|
||||
xray.transport.internet.StreamConfig stream_settings = 4;
|
||||
bool receive_original_destination = 5;
|
||||
reserved 6;
|
||||
// Override domains for the given protocol.
|
||||
// Deprecated. Use sniffing_settings.
|
||||
repeated KnownProtocols domain_override = 7 [ deprecated = true ];
|
||||
SniffingConfig sniffing_settings = 8;
|
||||
xray.transport.internet.StreamConfig stream_settings = 3;
|
||||
bool receive_original_destination = 4;
|
||||
reserved 5;
|
||||
SniffingConfig sniffing_settings = 6;
|
||||
}
|
||||
|
||||
message InboundHandlerConfig {
|
||||
@@ -92,6 +56,7 @@ message SenderConfig {
|
||||
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||
MultiplexingConfig multiplex_settings = 4;
|
||||
string via_cidr = 5;
|
||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||
}
|
||||
|
||||
message MultiplexingConfig {
|
||||
|
||||
@@ -5,15 +5,16 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/mux"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
|
||||
@@ -42,10 +43,12 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
||||
}
|
||||
|
||||
type AlwaysOnInboundHandler struct {
|
||||
proxy proxy.Inbound
|
||||
workers []worker
|
||||
mux *mux.Server
|
||||
tag string
|
||||
proxyConfig interface{}
|
||||
receiverConfig *proxyman.ReceiverConfig
|
||||
proxy proxy.Inbound
|
||||
workers []worker
|
||||
mux *mux.Server
|
||||
tag string
|
||||
}
|
||||
|
||||
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
|
||||
@@ -55,13 +58,15 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
}
|
||||
p, ok := rawProxy.(proxy.Inbound)
|
||||
if !ok {
|
||||
return nil, newError("not an inbound proxy.")
|
||||
return nil, errors.New("not an inbound proxy.")
|
||||
}
|
||||
|
||||
h := &AlwaysOnInboundHandler{
|
||||
proxy: p,
|
||||
mux: mux.NewServer(ctx),
|
||||
tag: tag,
|
||||
receiverConfig: receiverConfig,
|
||||
proxyConfig: proxyConfig,
|
||||
proxy: p,
|
||||
mux: mux.NewServer(ctx),
|
||||
tag: tag,
|
||||
}
|
||||
|
||||
uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag)
|
||||
@@ -75,7 +80,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse stream config").Base(err).AtWarning()
|
||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
@@ -89,7 +94,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
}
|
||||
if pl == nil {
|
||||
if net.HasNetwork(nl, net.Network_UNIX) {
|
||||
newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog()
|
||||
errors.LogDebug(ctx, "creating unix domain socket worker on ", address)
|
||||
|
||||
worker := &dsWorker{
|
||||
address: address,
|
||||
@@ -97,7 +102,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
stream: mss,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -109,7 +114,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
for _, pr := range pl.Range {
|
||||
for port := pr.From; port <= pr.To; port++ {
|
||||
if net.HasNetwork(nl, net.Network_TCP) {
|
||||
newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog()
|
||||
errors.LogDebug(ctx, "creating stream worker on ", address, ":", port)
|
||||
|
||||
worker := &tcpWorker{
|
||||
address: address,
|
||||
@@ -119,7 +124,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -134,7 +139,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
address: address,
|
||||
port: net.Port(port),
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(),
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: mss,
|
||||
@@ -167,19 +172,11 @@ func (h *AlwaysOnInboundHandler) Close() error {
|
||||
}
|
||||
errs = append(errs, h.mux.Close())
|
||||
if err := errors.Combine(errs...); err != nil {
|
||||
return newError("failed to close all resources").Base(err)
|
||||
return errors.New("failed to close all resources").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
|
||||
if len(h.workers) == 0 {
|
||||
return nil, 0, 0
|
||||
}
|
||||
w := h.workers[dice.Roll(len(h.workers))]
|
||||
return w.Proxy(), w.Port(), 9999
|
||||
}
|
||||
|
||||
func (h *AlwaysOnInboundHandler) Tag() string {
|
||||
return h.tag
|
||||
}
|
||||
@@ -187,3 +184,16 @@ func (h *AlwaysOnInboundHandler) Tag() string {
|
||||
func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound {
|
||||
return h.proxy
|
||||
}
|
||||
|
||||
// ReceiverSettings implements inbound.Handler.
|
||||
func (h *AlwaysOnInboundHandler) ReceiverSettings() *serial.TypedMessage {
|
||||
return serial.ToTypedMessage(h.receiverConfig)
|
||||
}
|
||||
|
||||
// ProxySettings implements inbound.Handler.
|
||||
func (h *AlwaysOnInboundHandler) ProxySettings() *serial.TypedMessage {
|
||||
if v, ok := h.proxyConfig.(proto.Message); ok {
|
||||
return serial.ToTypedMessage(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/mux"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
type DynamicInboundHandler struct {
|
||||
tag string
|
||||
v *core.Instance
|
||||
proxyConfig interface{}
|
||||
receiverConfig *proxyman.ReceiverConfig
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
portMutex sync.Mutex
|
||||
portsInUse map[net.Port]bool
|
||||
workerMutex sync.RWMutex
|
||||
worker []worker
|
||||
lastRefresh time.Time
|
||||
mux *mux.Server
|
||||
task *task.Periodic
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
h := &DynamicInboundHandler{
|
||||
tag: tag,
|
||||
proxyConfig: proxyConfig,
|
||||
receiverConfig: receiverConfig,
|
||||
portsInUse: make(map[net.Port]bool),
|
||||
mux: mux.NewServer(ctx),
|
||||
v: v,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse stream settings").Base(err).AtWarning()
|
||||
}
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
if mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
}
|
||||
if mss.SocketSettings.Tproxy == internet.SocketConfig_Off {
|
||||
mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect
|
||||
}
|
||||
mss.SocketSettings.ReceiveOriginalDestAddress = true
|
||||
}
|
||||
|
||||
h.streamSettings = mss
|
||||
|
||||
h.task = &task.Periodic{
|
||||
Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()),
|
||||
Execute: h.refresh,
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) allocatePort() net.Port {
|
||||
allPorts := []int32{}
|
||||
for _, pr := range h.receiverConfig.PortList.Range {
|
||||
for i := pr.From; i <= pr.To; i++ {
|
||||
allPorts = append(allPorts, int32(i))
|
||||
}
|
||||
}
|
||||
h.portMutex.Lock()
|
||||
defer h.portMutex.Unlock()
|
||||
|
||||
for {
|
||||
r := dice.Roll(len(allPorts))
|
||||
port := net.Port(allPorts[r])
|
||||
_, used := h.portsInUse[port]
|
||||
if !used {
|
||||
h.portsInUse[port] = true
|
||||
return port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) closeWorkers(workers []worker) {
|
||||
ports2Del := make([]net.Port, len(workers))
|
||||
for idx, worker := range workers {
|
||||
ports2Del[idx] = worker.Port()
|
||||
if err := worker.Close(); err != nil {
|
||||
newError("failed to close worker").Base(err).WriteToLog()
|
||||
}
|
||||
}
|
||||
|
||||
h.portMutex.Lock()
|
||||
for _, port := range ports2Del {
|
||||
delete(h.portsInUse, port)
|
||||
}
|
||||
h.portMutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) refresh() error {
|
||||
h.lastRefresh = time.Now()
|
||||
|
||||
timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2
|
||||
concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue()
|
||||
workers := make([]worker, 0, concurrency)
|
||||
|
||||
address := h.receiverConfig.Listen.AsAddress()
|
||||
if address == nil {
|
||||
address = net.AnyIP
|
||||
}
|
||||
|
||||
uplinkCounter, downlinkCounter := getStatCounter(h.v, h.tag)
|
||||
|
||||
for i := uint32(0); i < concurrency; i++ {
|
||||
port := h.allocatePort()
|
||||
rawProxy, err := core.CreateObject(h.v, h.proxyConfig)
|
||||
if err != nil {
|
||||
newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
p := rawProxy.(proxy.Inbound)
|
||||
nl := p.Network()
|
||||
if net.HasNetwork(nl, net.Network_TCP) {
|
||||
worker := &tcpWorker{
|
||||
tag: h.tag,
|
||||
address: address,
|
||||
port: port,
|
||||
proxy: p,
|
||||
stream: h.streamSettings,
|
||||
recvOrigDest: h.receiverConfig.ReceiveOriginalDestination,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: h.receiverConfig.GetEffectiveSniffingSettings(),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: h.ctx,
|
||||
}
|
||||
if err := worker.Start(); err != nil {
|
||||
newError("failed to create TCP worker").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
workers = append(workers, worker)
|
||||
}
|
||||
|
||||
if net.HasNetwork(nl, net.Network_UDP) {
|
||||
worker := &udpWorker{
|
||||
tag: h.tag,
|
||||
proxy: p,
|
||||
address: address,
|
||||
port: port,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: h.receiverConfig.GetEffectiveSniffingSettings(),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: h.streamSettings,
|
||||
ctx: h.ctx,
|
||||
}
|
||||
if err := worker.Start(); err != nil {
|
||||
newError("failed to create UDP worker").Base(err).AtWarning().WriteToLog()
|
||||
continue
|
||||
}
|
||||
workers = append(workers, worker)
|
||||
}
|
||||
}
|
||||
|
||||
h.workerMutex.Lock()
|
||||
h.worker = workers
|
||||
h.workerMutex.Unlock()
|
||||
|
||||
time.AfterFunc(timeout, func() {
|
||||
h.closeWorkers(workers)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Start() error {
|
||||
return h.task.Start()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Close() error {
|
||||
return h.task.Close()
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
|
||||
h.workerMutex.RLock()
|
||||
defer h.workerMutex.RUnlock()
|
||||
|
||||
if len(h.worker) == 0 {
|
||||
return nil, 0, 0
|
||||
}
|
||||
w := h.worker[dice.Roll(len(h.worker))]
|
||||
expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
|
||||
return w.Proxy(), w.Port(), int(expire)
|
||||
}
|
||||
|
||||
func (h *DynamicInboundHandler) Tag() string {
|
||||
return h.tag
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
package inbound
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/inbound"
|
||||
)
|
||||
|
||||
// Manager is to manage all inbound handlers.
|
||||
// Manager manages all inbound handlers.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
untaggedHandler []inbound.Handler
|
||||
untaggedHandlers []inbound.Handler
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
}
|
||||
@@ -43,11 +43,11 @@ func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error
|
||||
tag := handler.Tag()
|
||||
if len(tag) > 0 {
|
||||
if _, found := m.taggedHandlers[tag]; found {
|
||||
return newError("existing tag found: " + tag)
|
||||
return errors.New("existing tag found: " + tag)
|
||||
}
|
||||
m.taggedHandlers[tag] = handler
|
||||
} else {
|
||||
m.untaggedHandler = append(m.untaggedHandler, handler)
|
||||
m.untaggedHandlers = append(m.untaggedHandlers, handler)
|
||||
}
|
||||
|
||||
if m.running {
|
||||
@@ -64,7 +64,7 @@ func (m *Manager) GetHandler(ctx context.Context, tag string) (inbound.Handler,
|
||||
|
||||
handler, found := m.taggedHandlers[tag]
|
||||
if !found {
|
||||
return nil, newError("handler not found: ", tag)
|
||||
return nil, errors.New("handler not found: ", tag)
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
|
||||
|
||||
if handler, found := m.taggedHandlers[tag]; found {
|
||||
if err := handler.Close(); err != nil {
|
||||
newError("failed to close handler ", tag).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogWarningInner(ctx, err, "failed to close handler ", tag)
|
||||
}
|
||||
delete(m.taggedHandlers, tag)
|
||||
return nil
|
||||
@@ -89,6 +89,21 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
|
||||
return common.ErrNoClue
|
||||
}
|
||||
|
||||
// ListHandlers implements inbound.Manager.
|
||||
func (m *Manager) ListHandlers(ctx context.Context) []inbound.Handler {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
response := make([]inbound.Handler, len(m.untaggedHandlers))
|
||||
copy(response, m.untaggedHandlers)
|
||||
|
||||
for _, v := range m.taggedHandlers {
|
||||
response = append(response, v)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (m *Manager) Start() error {
|
||||
m.access.Lock()
|
||||
@@ -102,7 +117,7 @@ func (m *Manager) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, handler := range m.untaggedHandler {
|
||||
for _, handler := range m.untaggedHandlers {
|
||||
if err := handler.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,20 +132,20 @@ func (m *Manager) Close() error {
|
||||
|
||||
m.running = false
|
||||
|
||||
var errors []interface{}
|
||||
var errs []interface{}
|
||||
for _, handler := range m.taggedHandlers {
|
||||
if err := handler.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for _, handler := range m.untaggedHandler {
|
||||
for _, handler := range m.untaggedHandlers {
|
||||
if err := handler.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all handlers").Base(newError(serial.Concat(errors...)))
|
||||
if len(errs) > 0 {
|
||||
return errors.New("failed to close all handlers").Base(errors.New(serial.Concat(errs...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -150,7 +165,7 @@ func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound
|
||||
|
||||
receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
|
||||
if !ok {
|
||||
return nil, newError("not a ReceiverConfig").AtError()
|
||||
return nil, errors.New("not a ReceiverConfig").AtError()
|
||||
}
|
||||
|
||||
streamSettings := receiverSettings.StreamSettings
|
||||
@@ -159,16 +174,11 @@ func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound
|
||||
Mark: streamSettings.SocketSettings.Mark,
|
||||
})
|
||||
}
|
||||
|
||||
allocStrategy := receiverSettings.AllocationStrategy
|
||||
if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
|
||||
return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
|
||||
if streamSettings != nil && streamSettings.ProtocolName == "splithttp" {
|
||||
ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)
|
||||
}
|
||||
|
||||
if allocStrategy.Type == proxyman.AllocationStrategy_Random {
|
||||
return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
|
||||
}
|
||||
return nil, newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
|
||||
return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
@@ -58,7 +60,7 @@ func getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyM
|
||||
func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
ctx, cancel := context.WithCancel(w.ctx)
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
ctx = c.ContextWithID(ctx, sid)
|
||||
|
||||
outbounds := []*session.Outbound{{}}
|
||||
if w.recvOrigDest {
|
||||
@@ -67,14 +69,32 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
case internet.SocketConfig_Redirect:
|
||||
d, err := tcp.GetOriginalDestination(conn)
|
||||
if err != nil {
|
||||
newError("failed to get original destination").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfoInner(ctx, err, "failed to get original destination")
|
||||
} else {
|
||||
dest = d
|
||||
}
|
||||
case internet.SocketConfig_TProxy:
|
||||
dest = net.DestinationFromAddr(conn.LocalAddr())
|
||||
}
|
||||
|
||||
if dest.IsValid() {
|
||||
// Check if try to connect to this inbound itself (can cause loopback)
|
||||
var isLoopBack bool
|
||||
if w.address == net.AnyIP || w.address == net.AnyIPv6 {
|
||||
if dest.Port.Value() == w.port.Value() && IsLocal(dest.Address.IP()) {
|
||||
isLoopBack = true
|
||||
}
|
||||
} else {
|
||||
if w.hub.Addr().String() == dest.NetAddr() {
|
||||
isLoopBack = true
|
||||
}
|
||||
}
|
||||
if isLoopBack {
|
||||
cancel()
|
||||
conn.Close()
|
||||
errors.LogError(ctx, errors.New("loopback connection detected"))
|
||||
return
|
||||
}
|
||||
outbounds[0].Target = dest
|
||||
}
|
||||
}
|
||||
@@ -89,6 +109,7 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
}
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(conn.RemoteAddr()),
|
||||
Local: net.DestinationFromAddr(conn.LocalAddr()),
|
||||
Gateway: net.TCPDestination(w.address, w.port),
|
||||
Tag: w.tag,
|
||||
Conn: conn,
|
||||
@@ -105,7 +126,7 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfoInner(ctx, err, "connection ends")
|
||||
}
|
||||
cancel()
|
||||
conn.Close()
|
||||
@@ -121,24 +142,24 @@ func (w *tcpWorker) Start() error {
|
||||
go w.callback(conn)
|
||||
})
|
||||
if err != nil {
|
||||
return newError("failed to listen TCP on ", w.port).AtWarning().Base(err)
|
||||
return errors.New("failed to listen TCP on ", w.port).AtWarning().Base(err)
|
||||
}
|
||||
w.hub = hub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *tcpWorker) Close() error {
|
||||
var errors []interface{}
|
||||
var errs []interface{}
|
||||
if w.hub != nil {
|
||||
if err := common.Close(w.hub); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
if len(errs) > 0 {
|
||||
return errors.New("failed to close all resources").Base(errors.New(serial.Concat(errs...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -159,6 +180,7 @@ type udpConn struct {
|
||||
uplink stats.Counter
|
||||
downlink stats.Counter
|
||||
inactive bool
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (c *udpConn) setInactive() {
|
||||
@@ -201,6 +223,9 @@ func (c *udpConn) Write(buf []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (c *udpConn) Close() error {
|
||||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
}
|
||||
common.Must(c.done.Close())
|
||||
common.Must(common.Close(c.writer))
|
||||
return nil
|
||||
@@ -257,6 +282,7 @@ func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
|
||||
defer w.Unlock()
|
||||
|
||||
if conn, found := w.activeConn[id]; found && !conn.done.Done() {
|
||||
conn.updateActivity()
|
||||
return conn, true
|
||||
}
|
||||
|
||||
@@ -304,17 +330,28 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
||||
common.Must(w.checker.Start())
|
||||
|
||||
go func() {
|
||||
ctx := w.ctx
|
||||
ctx, cancel := context.WithCancel(w.ctx)
|
||||
conn.cancel = cancel
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
ctx = c.ContextWithID(ctx, sid)
|
||||
|
||||
outbounds := []*session.Outbound{{}}
|
||||
if originalDest.IsValid() {
|
||||
outbounds[0].Target = originalDest
|
||||
}
|
||||
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||
local := net.DestinationFromAddr(w.hub.Addr())
|
||||
if local.Address == net.AnyIP || local.Address == net.AnyIPv6 {
|
||||
if source.Address.Family().IsIPv4() {
|
||||
local.Address = net.AnyIP
|
||||
} else if source.Address.Family().IsIPv6() {
|
||||
local.Address = net.AnyIPv6
|
||||
}
|
||||
}
|
||||
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: source,
|
||||
Local: local, // Due to some limitations, in UDP connections, localIP is always equal to listen interface IP
|
||||
Gateway: net.UDPDestination(w.address, w.port),
|
||||
Tag: w.tag,
|
||||
})
|
||||
@@ -322,12 +359,13 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfoInner(ctx, err, "connection ends")
|
||||
}
|
||||
conn.Close()
|
||||
// conn not removed by checker TODO may be lock worker here is better
|
||||
@@ -358,7 +396,7 @@ func (w *udpWorker) clean() error {
|
||||
defer w.Unlock()
|
||||
|
||||
if len(w.activeConn) == 0 {
|
||||
return newError("no more connections. stopping...")
|
||||
return errors.New("no more connections. stopping...")
|
||||
}
|
||||
|
||||
for addr, conn := range w.activeConn {
|
||||
@@ -402,26 +440,26 @@ func (w *udpWorker) Close() error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
var errors []interface{}
|
||||
var errs []interface{}
|
||||
|
||||
if w.hub != nil {
|
||||
if err := w.hub.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if w.checker != nil {
|
||||
if err := w.checker.Close(); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
if len(errs) > 0 {
|
||||
return errors.New("failed to close all resources").Base(errors.New(serial.Concat(errs...)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -452,7 +490,7 @@ type dsWorker struct {
|
||||
func (w *dsWorker) callback(conn stat.Connection) {
|
||||
ctx, cancel := context.WithCancel(w.ctx)
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
ctx = c.ContextWithID(ctx, sid)
|
||||
|
||||
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
||||
conn = &stat.CounterConnection{
|
||||
@@ -463,6 +501,7 @@ func (w *dsWorker) callback(conn stat.Connection) {
|
||||
}
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(conn.RemoteAddr()),
|
||||
Local: net.DestinationFromAddr(conn.LocalAddr()),
|
||||
Gateway: net.UnixDestination(w.address),
|
||||
Tag: w.tag,
|
||||
Conn: conn,
|
||||
@@ -479,11 +518,11 @@ func (w *dsWorker) callback(conn stat.Connection) {
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {
|
||||
newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfoInner(ctx, err, "connection ends")
|
||||
}
|
||||
cancel()
|
||||
if err := conn.Close(); err != nil {
|
||||
newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfoInner(ctx, err, "failed to close connection")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,25 +540,40 @@ func (w *dsWorker) Start() error {
|
||||
go w.callback(conn)
|
||||
})
|
||||
if err != nil {
|
||||
return newError("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err)
|
||||
return errors.New("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err)
|
||||
}
|
||||
w.hub = hub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *dsWorker) Close() error {
|
||||
var errors []interface{}
|
||||
var errs []interface{}
|
||||
if w.hub != nil {
|
||||
if err := common.Close(w.hub); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := common.Close(w.proxy); err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return newError("failed to close all resources").Base(newError(serial.Concat(errors...)))
|
||||
if len(errs) > 0 {
|
||||
return errors.New("failed to close all resources").Base(errors.New(serial.Concat(errs...)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsLocal(ip net.IP) bool {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok {
|
||||
if ipnet.IP.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user