Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4301a8106 | ||
|
|
71da72da34 | ||
|
|
c6ef2031fa | ||
|
|
a82933a7a8 | ||
|
|
76444ef17d | ||
|
|
fde33380be | ||
|
|
eedb6684a5 | ||
|
|
a335a5bcdf | ||
|
|
4a347d853e | ||
|
|
79f55fe7fd | ||
|
|
affe8fb719 | ||
|
|
22eee048d0 | ||
|
|
2b2a550a98 | ||
|
|
14427ce437 | ||
|
|
39c170892e | ||
|
|
b82276e6fa | ||
|
|
154e8ef09a | ||
|
|
97a2f7c306 | ||
|
|
8d39d6045a | ||
|
|
04b60773b9 | ||
|
|
774a80dc46 | ||
|
|
1cba257531 | ||
|
|
16cc2abe7a | ||
|
|
595a49ee3f | ||
|
|
f894f62eb2 | ||
|
|
a98d6a7594 | ||
|
|
9674dfa5dc | ||
|
|
00068cdcfd | ||
|
|
846f128689 | ||
|
|
2225edeb2e | ||
|
|
ef48007922 | ||
|
|
1d5257363c | ||
|
|
0238d1ab7e | ||
|
|
ffbb85dfff | ||
|
|
563aca39aa | ||
|
|
274f4df43a | ||
|
|
1fae7c9979 | ||
|
|
46b37c0753 | ||
|
|
f737f591b6 | ||
|
|
ebf8337ad7 | ||
|
|
e2d8a462a2 | ||
|
|
b09cd690f0 | ||
|
|
76bd79d97a | ||
|
|
c0d8645833 | ||
|
|
501a9e8198 | ||
|
|
8aa4736b01 | ||
|
|
d5e10a8210 | ||
|
|
1165ee52c7 | ||
|
|
8a66bbe1af | ||
|
|
c91d8a2734 | ||
|
|
09a315a0f1 | ||
|
|
2979f2ef6f | ||
|
|
8225cb7877 | ||
|
|
129ca1ec85 | ||
|
|
96b67c6162 | ||
|
|
ce02b30b2b | ||
|
|
2fbdfe920e | ||
|
|
7901407d69 | ||
|
|
e6cfdec9c6 | ||
|
|
7e0de2c4e6 | ||
|
|
803af55246 | ||
|
|
8e1a8fa7f0 | ||
|
|
fc6bf407c4 | ||
|
|
dc4e472f20 | ||
|
|
afff49e61a | ||
|
|
0f9ac2ac87 | ||
|
|
cce23b9661 | ||
|
|
4347ed68c3 | ||
|
|
64d68c8a84 | ||
|
|
210b7c959e | ||
|
|
e8322adbcc | ||
|
|
e523b02b58 | ||
|
|
13df028cd9 | ||
|
|
4550bea063 | ||
|
|
be8d4cbb8b | ||
|
|
ace0268a8c | ||
|
|
56fd0d7a4f | ||
|
|
91ee751b07 | ||
|
|
fa9903ace1 | ||
|
|
c2854352df | ||
|
|
b3472fb5b3 | ||
|
|
e373c9ee16 | ||
|
|
13b7b1761d | ||
|
|
352f322f61 | ||
|
|
be966c4f83 | ||
|
|
b8cbf2cdac | ||
|
|
05cf0e1a6f | ||
|
|
f90ef96a5d | ||
|
|
81c6c8c1fc | ||
|
|
7ebdffcdd3 | ||
|
|
1abe33cb16 | ||
|
|
965cbfbb7e | ||
|
|
aa332b4f4d | ||
|
|
14ee9c4632 | ||
|
|
ddfa89167d | ||
|
|
1f9ed7b314 | ||
|
|
dc45602661 | ||
|
|
27c0f389ee | ||
|
|
59d6bfd957 | ||
|
|
31b8661b7d | ||
|
|
8ae20497c6 | ||
|
|
a70b4f30b5 | ||
|
|
df4795c9c4 | ||
|
|
c21a73f950 | ||
|
|
29cd111a49 | ||
|
|
367a56d346 | ||
|
|
d81ac6368e | ||
|
|
a228b62d23 | ||
|
|
995ff9b059 | ||
|
|
7dc78cc170 | ||
|
|
7e218355f2 | ||
|
|
98c394c993 | ||
|
|
31b06cd713 | ||
|
|
d7266926d8 | ||
|
|
1df5a1cfb1 | ||
|
|
8658bd3601 | ||
|
|
17d1ad3716 | ||
|
|
728598ba8e | ||
|
|
218483f431 | ||
|
|
1f80e06d9d | ||
|
|
5e56064ec6 | ||
|
|
31f0213fc5 | ||
|
|
54b1fef164 | ||
|
|
7c18df0a6f | ||
|
|
916fd8254b | ||
|
|
9e249862dc | ||
|
|
3773c09f57 | ||
|
|
6798c652f9 | ||
|
|
382d5b61ba | ||
|
|
ac162eba66 | ||
|
|
05a60b3cb7 | ||
|
|
7f210e9319 | ||
|
|
481c00629a | ||
|
|
bf55ec9512 | ||
|
|
073fa6ac6b | ||
|
|
51315efb52 | ||
|
|
30cbf39e7f | ||
|
|
2973d88df3 | ||
|
|
8b2d7293fe | ||
|
|
215d9aa362 | ||
|
|
c3f27e8727 | ||
|
|
755339189d | ||
|
|
71cf0aa66d | ||
|
|
d695efd787 | ||
|
|
66129c2a24 | ||
|
|
0bc7115102 | ||
|
|
dca33a3ec1 | ||
|
|
9acbae2554 | ||
|
|
ccfff7b1f6 | ||
|
|
39ae4c5dd6 | ||
|
|
350357076d | ||
|
|
1e5e15f1f5 | ||
|
|
1a385a777e | ||
|
|
16278dae62 | ||
|
|
8294e46003 | ||
|
|
dedc66875b | ||
|
|
3045e239ff | ||
|
|
5363558118 | ||
|
|
13ba7cf0b8 | ||
|
|
a5a43e762d | ||
|
|
cb6101a835 | ||
|
|
255f84c969 | ||
|
|
7617d6c409 | ||
|
|
f75691b483 | ||
|
|
c5dbb79ed2 | ||
|
|
60ba3c64b2 | ||
|
|
080e58eeb7 | ||
|
|
5d228b9c27 | ||
|
|
e36e45054c | ||
|
|
ba87a2780d | ||
|
|
fccc3478c8 | ||
|
|
a76280c53b | ||
|
|
90ff03e27c | ||
|
|
5eed05989f | ||
|
|
ac939746bd | ||
|
|
b7532e89c2 | ||
|
|
74f44d6961 | ||
|
|
86fc27c233 | ||
|
|
4b6bb0e5f7 | ||
|
|
215337fd8a | ||
|
|
e54341567a | ||
|
|
1ce3e9ff2e | ||
|
|
ffdb5ceb6b | ||
|
|
faa78445b1 | ||
|
|
39170b74ca | ||
|
|
440a832ace | ||
|
|
0e3f06429b | ||
|
|
01cf8806e2 | ||
|
|
ee7c597bd8 | ||
|
|
a85e0c6d06 | ||
|
|
fee3b24df4 | ||
|
|
7f500d72c7 | ||
|
|
d8a9ec5732 | ||
|
|
4dca4c4f22 | ||
|
|
0e4294415e | ||
|
|
9df324f402 | ||
|
|
3b1236cfd0 | ||
|
|
699262d79e | ||
|
|
f6cfa1b84c | ||
|
|
1338bcbbc5 | ||
|
|
4215488cb6 | ||
|
|
ccd6e44c2b | ||
|
|
b12b0b95a0 | ||
|
|
948f3d5d24 | ||
|
|
cf4fc54dfd | ||
|
|
5a9e31bded | ||
|
|
b61c140c24 | ||
|
|
035ae46ee5 | ||
|
|
b57a38e81e | ||
|
|
be767ae300 | ||
|
|
607c8c2e9f | ||
|
|
d3dce7a3cc | ||
|
|
ff2bbdd837 | ||
|
|
b771b6c074 | ||
|
|
ff389647c0 | ||
|
|
49bec2d6aa | ||
|
|
ee359dc0d8 | ||
|
|
d4fc28dca3 | ||
|
|
1130f69a82 | ||
|
|
e73ed238cd | ||
|
|
c6c1d297af | ||
|
|
f78f8de15e | ||
|
|
fcbc52dc2e | ||
|
|
bbfa255dd3 | ||
|
|
18fcee52db | ||
|
|
d36d0b9491 | ||
|
|
fb8d7ec5b8 | ||
|
|
24341808ef | ||
|
|
353809f91c | ||
|
|
d2b2dfbf1e | ||
|
|
e027cf9d98 | ||
|
|
484b1657e8 | ||
|
|
7dc4161f2f | ||
|
|
18eb4af016 | ||
|
|
635ec315f7 | ||
|
|
37eb9ec045 | ||
|
|
7c799589df | ||
|
|
4f52d58d6e | ||
|
|
441e374563 | ||
|
|
3272f3cdca | ||
|
|
5b40a9f147 | ||
|
|
12d4fc8e98 | ||
|
|
ca74827aaf | ||
|
|
0b570130e8 | ||
|
|
5bdc514606 | ||
|
|
137efe20b8 | ||
|
|
0cbc3e0f63 | ||
|
|
7fbcf0a832 | ||
|
|
7283863fe8 | ||
|
|
198f82fb2c | ||
|
|
c219af4f4b | ||
|
|
0352982c14 | ||
|
|
cc6cd6dc37 | ||
|
|
9155471c26 | ||
|
|
d916fd7573 | ||
|
|
5528557964 | ||
|
|
8dfc3b8c43 | ||
|
|
84824731a7 | ||
|
|
7131937d06 | ||
|
|
a657eae599 | ||
|
|
e9ec6e8903 | ||
|
|
c4a9e79dc2 | ||
|
|
aeebf45390 | ||
|
|
b818851b38 | ||
|
|
f47db0ab1b | ||
|
|
256a2adc3e | ||
|
|
28522f2f07 | ||
|
|
a33ffd96fd | ||
|
|
61f7352312 | ||
|
|
def33382e3 | ||
|
|
1d0d64bd30 | ||
|
|
e7e8f6b771 | ||
|
|
4317b06428 | ||
|
|
e9c695f4ac | ||
|
|
da714e97e8 | ||
|
|
ee2186aba5 | ||
|
|
ca91f97c27 | ||
|
|
284cf38f0b | ||
|
|
dc3e4c8253 | ||
|
|
c7b9f61bd2 | ||
|
|
b26c4b97b2 | ||
|
|
5569c67ba4 | ||
|
|
69da6de1ed | ||
|
|
ae983f014b | ||
|
|
499e3bbfdf | ||
|
|
01f6890fd3 | ||
|
|
9badbe180c | ||
|
|
30b1e71aa1 | ||
|
|
b36cc352d5 | ||
|
|
12d888cefc | ||
|
|
ad1df41029 | ||
|
|
67498dfc82 | ||
|
|
cf52fdde57 | ||
|
|
a20082d40b | ||
|
|
8e29b020e0 | ||
|
|
20132861e9 | ||
|
|
a1e9b3c133 | ||
|
|
8a4d21849e | ||
|
|
d2c5aac178 | ||
|
|
066e655c30 | ||
|
|
8627046deb | ||
|
|
76b86bbd0f | ||
|
|
e78994ceb4 | ||
|
|
c63813e590 | ||
|
|
39becec2e5 | ||
|
|
44e379df82 | ||
|
|
b2b58a23c1 | ||
|
|
03529bc339 | ||
|
|
996a80e710 | ||
|
|
ac837077f6 | ||
|
|
b25f330b0c | ||
|
|
c783a96e13 | ||
|
|
999a5b9117 | ||
|
|
6850681352 | ||
|
|
f2b21c9e4a | ||
|
|
d27f065507 | ||
|
|
5e913568a3 | ||
|
|
97b6db3485 | ||
|
|
ae63ce80b5 | ||
|
|
c0e930a3ef | ||
|
|
f8ee8e242f | ||
|
|
20e3ae4d58 | ||
|
|
a910d7a673 | ||
|
|
a4b5b2a028 | ||
|
|
a8a1346c98 | ||
|
|
734e613452 | ||
|
|
e2003d8dea | ||
|
|
f0a2edadfa | ||
|
|
4c174c3aac | ||
|
|
147156d96d | ||
|
|
64d62272a6 | ||
|
|
91713f0fed | ||
|
|
e7f2571469 | ||
|
|
48b41c4b22 | ||
|
|
042cc7c797 | ||
|
|
24c1edff8c | ||
|
|
ec2a74d06f | ||
|
|
63316ee23e | ||
|
|
f0139c68f7 | ||
|
|
590721a4da | ||
|
|
521280537a | ||
|
|
1eb2a0f5cf | ||
|
|
f0ac70c995 | ||
|
|
2570a9ead2 | ||
|
|
54da8ac71f | ||
|
|
bd73ed213e | ||
|
|
20fd060821 | ||
|
|
e8df40925a | ||
|
|
2c0a5d459a | ||
|
|
b67814f125 | ||
|
|
e3f328cc9b | ||
|
|
b6aba36510 | ||
|
|
22720a8f8f | ||
|
|
cc717a4060 | ||
|
|
5264455eba | ||
|
|
b0d6b1f884 | ||
|
|
7cd8647c0c | ||
|
|
19187aff47 | ||
|
|
23b536f771 | ||
|
|
119f719a9f | ||
|
|
9673eaab05 | ||
|
|
390b8dd242 | ||
|
|
1b1663c87e | ||
|
|
7bc4203deb | ||
|
|
c798c36914 | ||
|
|
14e7b518b5 | ||
|
|
88eb3f6514 | ||
|
|
d78e48211d | ||
|
|
55a510aa34 | ||
|
|
8d7eb5ba1b | ||
|
|
ceadadee68 | ||
|
|
e3e2a262e4 | ||
|
|
5850a59ec9 | ||
|
|
318c267c34 | ||
|
|
98bd713a91 | ||
|
|
fe499d0476 | ||
|
|
ff665d7dcf | ||
|
|
573537fbfb | ||
|
|
9e1bd28e2d | ||
|
|
27a6c686a7 | ||
|
|
2f6d743931 | ||
|
|
ec3aa01b9c | ||
|
|
02b988e898 | ||
|
|
b57cd2ed45 | ||
|
|
af5d284470 | ||
|
|
e7f1165287 | ||
|
|
e64e17267d | ||
|
|
7098111ad1 | ||
|
|
57b903ea29 | ||
|
|
d06082fc7a | ||
|
|
77a78daac1 | ||
|
|
d72fab04a2 | ||
|
|
76eaead0b9 | ||
|
|
858ca9bbe7 | ||
|
|
cbc4a7378c | ||
|
|
ee34f525e6 | ||
|
|
698e2531e8 | ||
|
|
16f833ec72 | ||
|
|
94d6caef7e | ||
|
|
364012f9dd | ||
|
|
cf4b1f418f | ||
|
|
c6465977ef | ||
|
|
c577e6381f | ||
|
|
134e31f563 | ||
|
|
0580b89f48 | ||
|
|
37080af275 | ||
|
|
26d68ac059 | ||
|
|
6292c1c381 | ||
|
|
b0c3f25a29 | ||
|
|
d2e09d3da5 | ||
|
|
293c5b90a4 | ||
|
|
e2323361f9 | ||
|
|
d05cff99ee | ||
|
|
e9b1b2ef3b | ||
|
|
a8ea2c14fc | ||
|
|
0688cb4515 | ||
|
|
c3261d7a57 | ||
|
|
ef8d13f61b | ||
|
|
fc104d5dee | ||
|
|
9ad0525216 | ||
|
|
3ea1250b53 | ||
|
|
9df34207f7 | ||
|
|
dacce892ff | ||
|
|
a9495f1d30 | ||
|
|
0892c6eee9 | ||
|
|
f746eadcf1 | ||
|
|
da2371541c | ||
|
|
cca91d4331 | ||
|
|
11aa3b1d2c | ||
|
|
d0b0c61a03 | ||
|
|
b0501db4e5 | ||
|
|
465a5c75ae | ||
|
|
978460aca8 | ||
|
|
4b86752c79 | ||
|
|
bd61cad7b6 | ||
|
|
40cbd2e26b | ||
|
|
2cab580b4e | ||
|
|
3d243fb01d | ||
|
|
ca4e8d14f0 | ||
|
|
9c098c55c9 | ||
|
|
707b07b8d3 | ||
|
|
de5a6971f0 | ||
|
|
056036c4d2 | ||
|
|
1cfb188845 | ||
|
|
73a2c31854 | ||
|
|
92eea536f8 | ||
|
|
00936a923e | ||
|
|
ba69ba78af | ||
|
|
17b206bace | ||
|
|
58d5a4f5da | ||
|
|
2438d14175 | ||
|
|
99f8526799 | ||
|
|
56fcbd64ed | ||
|
|
3766b2b7c1 | ||
|
|
baf0698fe4 | ||
|
|
301938b9fc | ||
|
|
16a37c713a | ||
|
|
d412a769dd | ||
|
|
c8eecc87fd | ||
|
|
6a11db70c8 | ||
|
|
d7c121a756 | ||
|
|
3429ee8bd6 | ||
|
|
4422f9f8b7 | ||
|
|
57ed454c93 | ||
|
|
236205c0f3 | ||
|
|
99b4bf0041 | ||
|
|
9495e39700 | ||
|
|
75e244e3a8 | ||
|
|
bf8397e6e3 | ||
|
|
8cee974866 | ||
|
|
5e018801db | ||
|
|
c4f7f4c19e | ||
|
|
29945eb3da | ||
|
|
7db415d243 | ||
|
|
808dcec795 | ||
|
|
ad8fecc319 | ||
|
|
27ae9a89da | ||
|
|
b3a4a4de5e | ||
|
|
ca76c35f6a | ||
|
|
073c820da6 | ||
|
|
954f0d2d05 | ||
|
|
a943f5bffc | ||
|
|
c3cff7c5b5 | ||
|
|
acb5a8a325 | ||
|
|
1a4e375dbf | ||
|
|
eec7d73ebe | ||
|
|
f4dda173c2 | ||
|
|
96fb8c881d | ||
|
|
aeebbd18fd | ||
|
|
baa71a51eb | ||
|
|
84e70149a1 | ||
|
|
959f2aa783 | ||
|
|
99b2aa9522 | ||
|
|
561fa9f8fe | ||
|
|
ad9dd6a7f7 | ||
|
|
65758eb94c | ||
|
|
fb7ccf4d11 | ||
|
|
b9fe5e0fe2 | ||
|
|
c3368481ad | ||
|
|
cdf5c3a508 | ||
|
|
f9217625e1 | ||
|
|
323f6c9221 | ||
|
|
2e507f1a9d | ||
|
|
e611d69a25 | ||
|
|
45d8c6717d | ||
|
|
756539c3bd | ||
|
|
3d4fcdbc96 | ||
|
|
3cd19dbc1f | ||
|
|
7b770a1bb0 | ||
|
|
0472b1590e | ||
|
|
81764433d8 | ||
|
|
9ee6e101c4 | ||
|
|
565e80b994 | ||
|
|
7ccc4535e4 | ||
|
|
d1bde297d6 | ||
|
|
c5398a51e8 | ||
|
|
fddb59992b | ||
|
|
665e25b1a3 | ||
|
|
fdb4bb1155 | ||
|
|
f104e191ed | ||
|
|
6311d75ac0 | ||
|
|
c3f134e332 | ||
|
|
5bccdf3bbe | ||
|
|
9e881eeecd | ||
|
|
fbe01d8cbf | ||
|
|
d336d938a9 | ||
|
|
598923c95a | ||
|
|
0afe4c4417 | ||
|
|
dff51caf35 | ||
|
|
7c30f2691a | ||
|
|
c6aca2c2dc | ||
|
|
28ac8cf88e | ||
|
|
62b2af54a2 | ||
|
|
a2d45da44d | ||
|
|
d937f392d3 | ||
|
|
2c9abf314a | ||
|
|
ab7abd0c1f | ||
|
|
71a2e3c592 | ||
|
|
aa6d3a6843 | ||
|
|
9b42c5d092 | ||
|
|
2f7817ce5b | ||
|
|
e7c6bcf9a9 | ||
|
|
b6b8971b12 | ||
|
|
ba4a0e008b | ||
|
|
4baccc5047 | ||
|
|
6cf71c8068 | ||
|
|
8e556a20ee | ||
|
|
5f28a5044a | ||
|
|
16d295bf32 | ||
|
|
32163e077e | ||
|
|
6a5a40a7d3 | ||
|
|
161ecc865c | ||
|
|
1a5d40ff2d | ||
|
|
6b988e0612 | ||
|
|
8d44934382 | ||
|
|
6b140b0f71 | ||
|
|
c2b7b02f3f | ||
|
|
24140c2145 | ||
|
|
a6050e5f59 | ||
|
|
50e97fd95f | ||
|
|
890e5f7391 | ||
|
|
be3bf82e35 | ||
|
|
e8af503130 | ||
|
|
466174ef88 | ||
|
|
ba2c792e2a | ||
|
|
17f3fcf9b0 | ||
|
|
11d1ff4b42 | ||
|
|
b640bc50ef | ||
|
|
4bbd5282ee | ||
|
|
d7fd504f8f | ||
|
|
cf4375b405 | ||
|
|
45da060c1a | ||
|
|
4dc84914dd | ||
|
|
c1ee7ac11c | ||
|
|
409b57c171 | ||
|
|
ff4580b9bf | ||
|
|
6daa75d2f2 | ||
|
|
59f07f2d75 | ||
|
|
44035685c8 | ||
|
|
c8d0f3850f | ||
|
|
b846f54d79 | ||
|
|
bb7b1d23af | ||
|
|
9e700b3094 | ||
|
|
38f28e313c | ||
|
|
63f03fb11f | ||
|
|
cf7854906f | ||
|
|
78ae5f3036 | ||
|
|
c412a256b8 | ||
|
|
94def558fc | ||
|
|
25e9c523d3 | ||
|
|
a009d06000 | ||
|
|
675e0c52ff | ||
|
|
dc4219753a | ||
|
|
c003b9acbb | ||
|
|
f4869ebd1d | ||
|
|
de4f42537d | ||
|
|
b9ebe80ef3 | ||
|
|
ea5ba750d4 | ||
|
|
b594afce16 | ||
|
|
ed3c44d6cf | ||
|
|
35ed7f7e45 | ||
|
|
068580e036 | ||
|
|
ea61b06e58 | ||
|
|
f260c26e4f | ||
|
|
9cea433a8b | ||
|
|
38dce9c13b | ||
|
|
04731f1ce5 | ||
|
|
3ddcb629c0 | ||
|
|
12bd058152 | ||
|
|
c9b1bf5a5e | ||
|
|
18df3c2c92 | ||
|
|
c08b9cefe9 | ||
|
|
258a35740f | ||
|
|
32cfe9a3a8 | ||
|
|
930a4fcf29 | ||
|
|
5a778d5fc3 | ||
|
|
1d64cfc930 | ||
|
|
3eab02bc68 | ||
|
|
dcdd03b746 | ||
|
|
51acb2da9b | ||
|
|
0d2c107e20 | ||
|
|
2089ba7997 | ||
|
|
8a032f86d8 | ||
|
|
5dcda4d088 | ||
|
|
8d76214193 | ||
|
|
24ddf04d7f | ||
|
|
79fa2a0823 | ||
|
|
ddfb5ac943 | ||
|
|
ed622409b0 | ||
|
|
b6beaa09ec | ||
|
|
7840f30b64 | ||
|
|
d7226d8522 | ||
|
|
8d5c9cedb3 | ||
|
|
6f7992ea9f | ||
|
|
5b2c2435d5 | ||
|
|
ebf84db204 | ||
|
|
94e83d80a6 | ||
|
|
bc80d5f641 | ||
|
|
81a5480250 | ||
|
|
e69d416dc4 | ||
|
|
15370203a1 | ||
|
|
8ca09d72bd | ||
|
|
d16cbe4ac6 | ||
|
|
49ac8827cd | ||
|
|
f2f686d31f | ||
|
|
6872ec3618 | ||
|
|
efb7053931 | ||
|
|
80109fd8aa | ||
|
|
b3abb175c1 | ||
|
|
f05577c927 | ||
|
|
c631f2bb0d | ||
|
|
ad5aac7ef5 | ||
|
|
5cd9179ce7 | ||
|
|
af58c3990d | ||
|
|
206c543e8c | ||
|
|
9820c56be6 | ||
|
|
2232965a1d | ||
|
|
6bfadfe5b8 | ||
|
|
c655d9650b | ||
|
|
61086089b3 | ||
|
|
c89d0124a1 | ||
|
|
a1a5839b20 | ||
|
|
c9e3dc83ab | ||
|
|
c179670b14 | ||
|
|
292893e9dd | ||
|
|
d956e493af | ||
|
|
05c85db52a | ||
|
|
49350933c3 | ||
|
|
fa3fed3fbd | ||
|
|
5df9a357ec | ||
|
|
bce9c57e60 | ||
|
|
f39de52c65 | ||
|
|
4001527a87 | ||
|
|
85be89ddf0 |
@@ -0,0 +1,22 @@
|
||||
[build]
|
||||
# target = ["x86_64-unknown-linux-musl"]
|
||||
# target = ["x86_64-unknown-linux-gnu"]
|
||||
# target = ["aarch64-linux-android"]
|
||||
# target = ["x86_64-linux-android"]
|
||||
# target = ["aarch64-apple-ios"]
|
||||
# target = ["x86_64-apple-ios"]
|
||||
# target = ["x86_64-pc-windows-msvc"]
|
||||
# target = ["x86_64-apple-darwin"]
|
||||
# target = ["x86_64-unknown-freebsd"]
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-linux-musl-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static", "-C", "strip=symbols"]
|
||||
|
||||
[target.mipsel-unknown-linux-musl]
|
||||
linker = "mipsel-linux-muslsf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
|
||||
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
|
||||
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib",
|
||||
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1"]
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# necessary for windows
|
||||
shell: bash
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.TARGET }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- TARGET: x86_64-unknown-linux-musl
|
||||
OS: ubuntu-latest
|
||||
- TARGET: aarch64-unknown-linux-musl
|
||||
OS: ubuntu-latest
|
||||
- TARGET: armv7-unknown-linux-musleabihf
|
||||
OS: ubuntu-latest
|
||||
- TARGET: armv7-unknown-linux-musleabi
|
||||
OS: ubuntu-latest
|
||||
- TARGET: arm-unknown-linux-musleabihf
|
||||
OS: ubuntu-latest
|
||||
- TARGET: arm-unknown-linux-musleabi
|
||||
OS: ubuntu-latest
|
||||
- TARGET: x86_64-apple-darwin
|
||||
OS: macos-latest
|
||||
- TARGET: aarch64-apple-darwin
|
||||
OS: macos-latest
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
- TARGET: aarch64-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
- TARGET: mipsel-unknown-linux-musl
|
||||
OS: ubuntu-latest
|
||||
- TARGET: mips-unknown-linux-musl
|
||||
OS: ubuntu-latest
|
||||
- TARGET: x86_64-unknown-freebsd
|
||||
OS: ubuntu-latest
|
||||
ARTIFACT_NAME: freebsd-13.2-x86_64
|
||||
BSD_VERSION: 13.2
|
||||
runs-on: ${{ matrix.OS }}
|
||||
env:
|
||||
NAME: Vnt
|
||||
TARGET: ${{ matrix.TARGET }}
|
||||
OS: ${{ matrix.OS }}
|
||||
FEATURES: ${{ matrix.FEATURES }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Init submodules
|
||||
run: git submodule update --init --recursive --remote && git submodule status
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
./target
|
||||
key: build-cargo-registry-${{matrix.TARGET}}
|
||||
- name: Install Protobuf (MacOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install protobuf
|
||||
- name: Install Protobuf (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: choco install protoc
|
||||
- name: Use strawberry perl
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: echo OPENSSL_SRC_PERL=C:/Strawberry/perl/bin/perl >> $GITHUB_ENV
|
||||
- name: List
|
||||
run: find ./
|
||||
- name: Build Vnt X86_64-FreeBSD
|
||||
uses: cross-platform-actions/[email protected]
|
||||
if: ${{ endsWith(matrix.TARGET, 'freebsd') }}
|
||||
env:
|
||||
TARGET: ${{ matrix.TARGET }}
|
||||
with:
|
||||
operating_system: freebsd
|
||||
environment_variables: TARGET
|
||||
architecture: x86-64
|
||||
version: ${{ matrix.BSD_VERSION }}
|
||||
shell: bash
|
||||
memory: 5G
|
||||
cpu_count: 4
|
||||
run: |
|
||||
uname -a
|
||||
echo $SHELL
|
||||
pwd
|
||||
ls -lah
|
||||
whoami
|
||||
env | sort
|
||||
sudo pkg install -y git protobuf llvm15
|
||||
curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source $HOME/.cargo/env
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
export CARGO_TERM_COLOR=always
|
||||
cargo build --release --verbose --bin vnt2_web --features vnt-web --target $TARGET
|
||||
cargo build --release --verbose --bin vnt2_cli --features vnt-ipc --target $TARGET
|
||||
cargo build --release --verbose --bin vnt2_ctrl --features vnt-ipc --target $TARGET
|
||||
- name: Install and configure dependencies
|
||||
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
|
||||
run: |
|
||||
# dependencies are only needed on ubuntu as that's the only place where
|
||||
# we make cross-compilation
|
||||
|
||||
if [[ $OS =~ ^ubuntu.*$ ]]; then
|
||||
sudo apt-get update && sudo apt-get install protobuf-compiler clang llvm libc6-dev-i386 crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools libboost-all-dev libc6-dev -y
|
||||
# curl -s musl.cc | grep mipsel
|
||||
case $TARGET in
|
||||
mipsel-unknown-linux-musl)
|
||||
MUSL_URI=mipsel-linux-muslsf-cross
|
||||
URL=mipsel-linux-muslsf
|
||||
;;
|
||||
aarch64-unknown-linux-musl)
|
||||
MUSL_URI=aarch64-linux-musl-cross
|
||||
;;
|
||||
armv7-unknown-linux-musleabihf)
|
||||
MUSL_URI=armv7l-linux-musleabihf-cross
|
||||
;;
|
||||
armv7-unknown-linux-musleabi)
|
||||
MUSL_URI=armv7m-linux-musleabi-cross
|
||||
;;
|
||||
arm-unknown-linux-musleabihf)
|
||||
MUSL_URI=arm-linux-musleabihf-cross
|
||||
;;
|
||||
arm-unknown-linux-musleabi)
|
||||
MUSL_URI=arm-linux-musleabi-cross
|
||||
;;
|
||||
mips-unknown-linux-musl)
|
||||
MUSL_URI=mips-linux-muslsf-cross
|
||||
URL=mips-linux-muslsf
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
if [ -n "$MUSL_URI" ]; then
|
||||
mkdir -p ./musl_gcc
|
||||
wget -c https://github.com/rustp2p/musl-cross/releases/download/0.0.1/$MUSL_URI.tgz -P /opt/musl_gcc/
|
||||
tar zxf /opt/musl_gcc/$MUSL_URI.tgz -C /opt/musl_gcc/
|
||||
sudo ln -s /opt/musl_gcc/$MUSL_URI/bin/*gcc /usr/bin/
|
||||
fi
|
||||
fi
|
||||
if [[ $TARGET =~ ^mips.*$ ]]; then
|
||||
cd /opt/musl_gcc/${URL}-cross/lib/gcc/${URL}/11.2.1
|
||||
cp libgcc_eh.a libunwind.a
|
||||
rustup toolchain install nightly-x86_64-unknown-linux-gnu
|
||||
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
RUST_LIB_SRC=$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/
|
||||
if [[ -f $RUST_LIB_SRC/library/Cargo.lock && ! -f $RUST_LIB_SRC/Cargo.lock ]]; then
|
||||
cp -f $RUST_LIB_SRC/library/Cargo.lock $RUST_LIB_SRC/Cargo.lock
|
||||
fi
|
||||
fi
|
||||
rustup -V
|
||||
|
||||
# some additional configuration for cross-compilation on linux
|
||||
cat >>~/.cargo/config.toml <<EOF
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-linux-musl-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.armv7-unknown-linux-musleabihf]
|
||||
linker = "armv7l-linux-musleabihf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.armv7-unknown-linux-musleabi]
|
||||
linker = "armv7m-linux-musleabi-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.arm-unknown-linux-musleabihf]
|
||||
linker = "arm-linux-musleabihf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.arm-unknown-linux-musleabi]
|
||||
linker = "arm-linux-musleabi-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.mipsel-unknown-linux-musl]
|
||||
linker = "mipsel-linux-muslsf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
|
||||
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
|
||||
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib",
|
||||
"-L", "/opt/musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1"]
|
||||
[target.mips-unknown-linux-musl]
|
||||
linker = "mips-linux-muslsf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols","-C", "link-arg=-static",
|
||||
"-C", "relocation-model=static","-C", "link-arg=-no-pie","--cfg", "compiler_builtins_no_debug",
|
||||
"-L", "/opt/musl_gcc/mips-linux-muslsf-cross/mips-linux-muslsf/lib",
|
||||
"-L", "/opt/musl_gcc/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1"]
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.i686-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
[target.i686-unknown-linux-gnu]
|
||||
rustflags = ["-C", "target-feature=+crt-static","-C", "strip=symbols"]
|
||||
EOF
|
||||
- name: Install rust target
|
||||
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') && ! startsWith(matrix.TARGET, 'mips') }}
|
||||
run: rustup target add $TARGET
|
||||
- name: Run build Vnt
|
||||
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
|
||||
run: |
|
||||
if [[ $TARGET =~ ^mips.*$ ]]; then
|
||||
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_web --features vnt-web
|
||||
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_cli --features vnt-ipc
|
||||
cargo +nightly build --release --verbose --target $TARGET -Z build-std=std,panic_abort --bin vnt2_ctrl --features vnt-ipc
|
||||
else
|
||||
cargo build --release --verbose --target $TARGET --bin vnt2_web --features vnt-web
|
||||
cargo build --release --verbose --target $TARGET --bin vnt2_cli --features vnt-ipc
|
||||
cargo build --release --verbose --target $TARGET --bin vnt2_ctrl --features vnt-ipc
|
||||
fi
|
||||
ldd ./target/$TARGET/release/vnt2_web || true
|
||||
file ./target/$TARGET/release/vnt2_web || true
|
||||
- name: List target
|
||||
run: find ./target
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
mkdir -p ./package
|
||||
if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
|
||||
TAG=$GITHUB_REF_NAME
|
||||
else
|
||||
TAG=$GITHUB_SHA
|
||||
fi
|
||||
ZIP_NAME="vnt2-${TARGET}-${TAG}.zip"
|
||||
|
||||
if [[ $OS =~ ^windows.*$ ]]; then
|
||||
cp ./target/$TARGET/release/vnt2_web.exe ./package/
|
||||
cp ./target/$TARGET/release/vnt2_cli.exe ./package/
|
||||
cp ./target/$TARGET/release/vnt2_ctrl.exe ./package/
|
||||
cd ./package
|
||||
7z a -tzip "../artifacts/$ZIP_NAME" ./*
|
||||
else
|
||||
cp ./target/$TARGET/release/vnt2_web ./package/
|
||||
cp ./target/$TARGET/release/vnt2_cli ./package/
|
||||
cp ./target/$TARGET/release/vnt2_ctrl ./package/
|
||||
cd ./package
|
||||
zip "../artifacts/$ZIP_NAME" ./*
|
||||
fi
|
||||
- name: List target Vnt
|
||||
run: find ./artifacts
|
||||
- name: Archive artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Vnt-${{ matrix.TARGET }}
|
||||
path: |
|
||||
./artifacts/*
|
||||
# deploys to github releases on tag
|
||||
deploy:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./artifacts
|
||||
- name: List
|
||||
run: find ./artifacts
|
||||
- name: Release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./artifacts/*/*.zip
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
@@ -0,0 +1,5 @@
|
||||
/target
|
||||
./wintun.dll
|
||||
vnt_config
|
||||
vnt_current_config.txt
|
||||
logs/*
|
||||
Generated
+3844
@@ -0,0 +1,3844 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
"displaydoc",
|
||||
"nom",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-impl"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-event"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1222afd3d2bce3995035054046a279ae7aa154d70d0766cea050073f3fd7ddf"
|
||||
dependencies = [
|
||||
"loom",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-scoped"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"pin-project",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "c2rust-bitfields"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcee50917f9de1a018e3f4f9a8f2ff3d030a288cffa4b18d9b391e97c12e4cfb"
|
||||
dependencies = [
|
||||
"c2rust-bitfields-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "c2rust-bitfields-derive"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b457277798202ccd365b9c112ebee08ddd57f1033916c8b8ea52f222e5b715d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "cli-table"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14da8d951cef7cc4f13ccc9b744d736963d57863c7e6fc33c070ea274546082c"
|
||||
dependencies = [
|
||||
"cli-table-derive",
|
||||
"csv",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cli-table-derive"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c1b60bae2c3d45228dfb096046aa51ef6c300de70b658d7a13fcb0c4f832e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc",
|
||||
"nom",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "destructure_traitobject"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
|
||||
|
||||
[[package]]
|
||||
name = "diatomic-waker"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28025fb55a9d815acf7b0877555f437254f373036eec6ed265116c7a5c0825e9"
|
||||
dependencies = [
|
||||
"loom",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dns-parser"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getifaddrs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06f770241660efeeedb30b48ccbe0a8e6f649322148237539e71f3ccd14a4461"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log-mdc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7"
|
||||
|
||||
[[package]]
|
||||
name = "log4rs"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e947bb896e702c711fccc2bf02ab2abb6072910693818d1d6b07ee2b9dfd86c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"fnv",
|
||||
"humantime",
|
||||
"libc",
|
||||
"log",
|
||||
"log-mdc",
|
||||
"mock_instant",
|
||||
"parking_lot 0.12.5",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror 2.0.18",
|
||||
"thread-id",
|
||||
"typemap-ors",
|
||||
"unicode-segmentation",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e"
|
||||
dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "machine-uid"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d7217d573cdb141d6da43113b098172e057d39915d79c4bdedbc3aacd46bd96"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-registry",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mock_instant"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6"
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netconfig-rs"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "207561c8758738388c2fd851c3e759f503583d8dd70ddc895baae806753ad4c9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation 0.9.4",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"netlink-packet-core 0.7.0",
|
||||
"netlink-packet-route 0.24.0",
|
||||
"netlink-sys",
|
||||
"nix 0.30.1",
|
||||
"scopeguard",
|
||||
"system-configuration-sys",
|
||||
"thiserror 2.0.18",
|
||||
"widestring",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"netlink-packet-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
|
||||
dependencies = [
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-route"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"netlink-packet-core 0.7.0",
|
||||
"netlink-packet-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-route"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"log",
|
||||
"netlink-packet-core 0.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-utils"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"paste",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-sys"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-net"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_base"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
||||
dependencies = [
|
||||
"no-std-net",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros_support"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
|
||||
dependencies = [
|
||||
"pnet_base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_packet"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"pnet_base",
|
||||
"pnet_macros",
|
||||
"pnet_macros_support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prettyplease",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.18",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.2",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-ex"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42776a943a4f890410259860d4c308d32792ecbeaca4932a2b94b04f6d27bb9a"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"time",
|
||||
"x509-parser",
|
||||
"yasna",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reed-solomon-erasure"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f"
|
||||
dependencies = [
|
||||
"libm",
|
||||
"lru",
|
||||
"parking_lot 0.11.2",
|
||||
"smallvec",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "route_manager"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "319bb478ff9aae1dc7a544fa599e9eb47e2521621dc3921fba6abcaaa312092c"
|
||||
dependencies = [
|
||||
"flume 0.12.0",
|
||||
"libc",
|
||||
"netlink-packet-core 0.8.1",
|
||||
"netlink-packet-route 0.28.0",
|
||||
"netlink-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-p2p-core"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eccee83a23618a90d50a11cf971e72ce68efee69dbca8c246795ecd791118e32"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-lock",
|
||||
"async-scoped",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"dashmap",
|
||||
"dyn-clone",
|
||||
"futures",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.12.5",
|
||||
"rand",
|
||||
"serde",
|
||||
"socket2 0.5.10",
|
||||
"stun-format",
|
||||
"tachyonix",
|
||||
"tokio",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-value"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||
dependencies = [
|
||||
"ordered-float",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "stun-bytes"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bebd666814c8af1e06e4d489f9c3d03a4aef20bae651550c52e6e95e09f3f12"
|
||||
dependencies = [
|
||||
"r-ex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stun-format"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6ad3c54fde6dfd6fe5048a71515b392d22c29730533663b5300e1b511d78df5"
|
||||
dependencies = [
|
||||
"r-ex",
|
||||
"stun-bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tachyonix"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86c3eafa053bbcc63bb4bfc5eb26362a33ea0bc2e589f28bce00287d1c167d45"
|
||||
dependencies = [
|
||||
"async-event",
|
||||
"crossbeam-utils",
|
||||
"diatomic-waker",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tcp_ip"
|
||||
version = "0.1.10"
|
||||
source = "git+https://github.com/rustp2p/tcp_ip#09c64c81da70a35248dfef4ef42bb9f5e8b6e05e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"flume 0.11.1",
|
||||
"log",
|
||||
"num_enum",
|
||||
"parking_lot 0.12.5",
|
||||
"pnet_packet",
|
||||
"rand",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.6.2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.11+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tun-rs"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a88c6aea62111e48fb67b733d6bbdc2019272e6d6f7108c9b0f4cd25d715d38a"
|
||||
dependencies = [
|
||||
"blocking",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"c2rust-bitfields",
|
||||
"encoding_rs",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"getifaddrs",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
"netconfig-rs",
|
||||
"nix 0.31.1",
|
||||
"route_manager",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"widestring",
|
||||
"windows-sys 0.61.2",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
|
||||
|
||||
[[package]]
|
||||
name = "typemap-ors"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867"
|
||||
dependencies = [
|
||||
"unsafe-any-ors",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-any-ors"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad"
|
||||
dependencies = [
|
||||
"destructure_traitobject",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vnt-core"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"dns-parser",
|
||||
"futures",
|
||||
"getifaddrs",
|
||||
"hex",
|
||||
"ipnet",
|
||||
"log",
|
||||
"lz4_flex",
|
||||
"machine-uid",
|
||||
"parking_lot 0.12.5",
|
||||
"pnet_packet",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"quinn",
|
||||
"rand",
|
||||
"rcgen",
|
||||
"reed-solomon-erasure",
|
||||
"ring",
|
||||
"rust-p2p-core",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"serde",
|
||||
"sha2",
|
||||
"socket2 0.6.2",
|
||||
"tcp_ip",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tun-rs",
|
||||
"tungstenite",
|
||||
"uuid",
|
||||
"widestring",
|
||||
"winapi",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vnt-ipc"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cli-table",
|
||||
"console",
|
||||
"futures",
|
||||
"log",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"vnt-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vnt-web"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"hostname",
|
||||
"ipnet",
|
||||
"log",
|
||||
"mime_guess",
|
||||
"parking_lot 0.12.5",
|
||||
"route_manager",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower-http",
|
||||
"vnt-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vnt2"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"hostname",
|
||||
"ipnet",
|
||||
"log",
|
||||
"log4rs",
|
||||
"route_manager",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml",
|
||||
"vnt-ipc",
|
||||
"vnt-web",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-future",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"data-encoding",
|
||||
"der-parser",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"oid-registry",
|
||||
"ring",
|
||||
"rusticata-macros",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||
dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
||||
+47
-34
@@ -1,44 +1,57 @@
|
||||
[package]
|
||||
name = "switch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
name = "vnt2"
|
||||
version = "2.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
packet = { path = "./packet" }
|
||||
bytes = "1.3.0"
|
||||
vnt-ipc = { path = "vnt-ipc", optional = true }
|
||||
vnt-web = { path = "vnt-web", optional = true }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
libc = "0.2.137"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
dashmap = "5.4.0"
|
||||
crossbeam = "0.8.2"
|
||||
parking_lot = "0.12.1"
|
||||
|
||||
rsa = "0.7.2"
|
||||
rand = "0.8.5"
|
||||
sha2 = { version = "0.10.6", features = ["oid"] }
|
||||
#colored = "2.0.0"
|
||||
log = "0.4"
|
||||
log4rs = "1.4"
|
||||
|
||||
thiserror = "1.0.37"
|
||||
chrono = "0.4.23"
|
||||
lazy_static = "1.4.0"
|
||||
moka = "0.9.6"
|
||||
protobuf = "3.2.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
anyhow = "1.0.100"
|
||||
toml = "0.9.8"
|
||||
hostname = "0.4.2"
|
||||
ipnet = { version = "2.11", features = ["serde"] }
|
||||
|
||||
console = "0.15.2"
|
||||
mac_address = "1.1.4"
|
||||
clap = { version = "4.0.32", features = ["derive"] }
|
||||
[target.'cfg(any(unix))'.dependencies]
|
||||
tun = { path = "./rust-tun" }
|
||||
sudo = "0.6.0"
|
||||
route_manager = "0.2.11"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["handleapi", "processthreadsapi", "winnt", "securitybaseapi", "impl-default"] }
|
||||
wintun = "0.2.1"
|
||||
libloading = "0.7.4"
|
||||
runas = "0.2.1"
|
||||
[features]
|
||||
default = []
|
||||
vnt-ipc = ["dep:vnt-ipc"]
|
||||
vnt-web = ["dep:vnt-web"]
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen = "3.2.0"
|
||||
protoc-bin-vendored = "3.0.0"
|
||||
[[bin]]
|
||||
name = "vnt2_cli"
|
||||
path = "src/main_cli.rs"
|
||||
required-features = ["vnt-ipc"]
|
||||
|
||||
[[bin]]
|
||||
name = "vnt2_ctrl"
|
||||
path = "src/main_ctrl.rs"
|
||||
required-features = ["vnt-ipc"]
|
||||
|
||||
[[bin]]
|
||||
name = "vnt2_web"
|
||||
path = "src/main_web.rs"
|
||||
required-features = ["vnt-web"]
|
||||
|
||||
[workspace]
|
||||
members = ["vnt-web", "vnt-ipc", "vnt-core"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
debug = 0
|
||||
debug-assertions = false
|
||||
strip = true
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
rpath = false
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
一、程序说明
|
||||
1. vnt-cli vnt的命令行程序
|
||||
2. vn-link-cli 功能和vnt-cli基本一致,但是不依赖tun、不改变本地路由、不需要管理员/root权限
|
||||
|
||||
二、使用说明
|
||||
使用-k参数构建虚拟网络
|
||||
|
||||
|
||||
1. Program Description
|
||||
a. vnt-cli: Command-line program for VNT.
|
||||
b. vn-link-cli: Functions similarly to vnt-cli, but does not depend on TUN, does not change local routing, and does not require administrator/root permissions.
|
||||
|
||||
2. Instructions for Use
|
||||
Use the -k parameter to create a virtual network.
|
||||
@@ -1,38 +1,63 @@
|
||||
# switch
|
||||
Virtual Network Tools
|
||||
# VNT
|
||||
|
||||
将不同网络下的设备虚拟到一个局域网下
|
||||
一个简单、高效、能快速组建虚拟局域网的工具
|
||||
|
||||
# 快速开始
|
||||
|
||||
### 简单说明
|
||||
1. vnt2_cli 是一个纯命令行组网工具,可以从命令行参数或配置文件快速启动组网
|
||||
2. vnt2_ctrl 和vnt2_cli搭配使用,vnt2_cli后台运行时,可以用vnt2_ctrl来获取组网状态
|
||||
3. vnt2_web 是一个集成web服务的组网工具,带web页面,可以在页面上操作组网
|
||||
|
||||
## 使用vnt2_cli组网
|
||||
|
||||
使用方式和vnt1.0一样,只是增减了一些功能,具体参数请查看 -h
|
||||
```
|
||||
# 启动程序 服务端可以使用101.35.230.139:6660
|
||||
./vnt2_cli -k 123456 -s 101.35.230.139:6660
|
||||
```
|
||||
|
||||
```
|
||||
# 查看组网信息
|
||||
./vnt2_ctrl info
|
||||
```
|
||||
|
||||
## 使用vnt2_web组网
|
||||
|
||||
1. 启动程序
|
||||
|
||||
```
|
||||
# 启动程序
|
||||
./vnt2_web
|
||||
```
|
||||
2. 浏览器打开 http://127.0.0.1:19099
|
||||
3. 在页面上添加组网配置,再启动组网
|
||||
|
||||
|
||||
### 示例:
|
||||
# VNT2.0新特性
|
||||
|
||||
- 在一台mac设备上运行,获取到ip 10.13.0.2:
|
||||
1. 提升安全性,支持tcp-tls、quic、wss协议连接服务器,和服务端强制使用tls加密,并支持证书绑定,防止伪造服务端攻击
|
||||
2. 提升流量稳定性,支持使用quic代理流量,支持FEC冗余传输
|
||||
3. 简化操作,去除了大量vnt1.0的重复和无用的配置参数
|
||||
4. vnt-link、vnt合二为一
|
||||
5. 支持有tun模式、无tun模式、端口映射
|
||||
6. 全功能的情况下,减少程序体积
|
||||
7. 性能提升,支持linux-offload
|
||||
8. 更规范的api接入,可以轻松自定义客户端
|
||||
9. 支持同时连接多个服务端,可以容灾和负载均衡
|
||||
|
||||
<img width="506" alt="图片" src="https://user-images.githubusercontent.com/49143209/210379090-a3f21007-5a12-44d3-81d6-a69495209ea7.png">
|
||||
# 说明
|
||||
|
||||
- 在另一台windows上运行,获取到ip 10.13.0.3:
|
||||
vnt2.0整体重构了一遍,和1.0不兼容,同时也可能引入新的bug,欢迎反馈
|
||||
|
||||

|
||||
其他平台后续再推出
|
||||
|
||||
- 此时这两个设备之间就能用ip相互访问了
|
||||
### 相关库
|
||||
1. tun虚拟网卡(https://github.com/tun-rs/tun-rs)
|
||||
2. 路由设置(https://github.com/tun-rs/route_manager)
|
||||
3. 用户态协议栈(用于quic代理和无tun模式出口)(https://github.com/rustp2p/tcp_ip)
|
||||
4. 打洞通道处理(https://github.com/rustp2p/rustp2p/tree/master/rustp2p-core)
|
||||
|
||||
<img width="437" alt="图片" src="https://user-images.githubusercontent.com/49143209/210380969-4a7c0f23-1e88-4ab6-9cc2-0c0f086848ac.png">
|
||||
|
||||
- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
|
||||
- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~
|
||||
### 支持平台
|
||||
- Mac
|
||||
- Linux
|
||||
- Windows
|
||||
- 依赖 wintun.dll(https://www.wintun.net/)
|
||||
|
||||
### 特性
|
||||
- IP层数据转发
|
||||
- tun虚拟网卡
|
||||
- NAT穿透
|
||||
- 点对点穿透
|
||||
- 服务端中继转发
|
||||
|
||||
### Todo
|
||||
- 数据加密
|
||||
- 客户端中继转发
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
fn main() {
|
||||
std::fs::create_dir_all("src/proto").unwrap();
|
||||
protobuf_codegen::Codegen::new()
|
||||
.pure()
|
||||
.out_dir("src/proto")
|
||||
.inputs(&["proto/message.proto"])
|
||||
.include("proto")
|
||||
// .customize(
|
||||
// protobuf_codegen::Customize::default()
|
||||
// .tokio_bytes(true)
|
||||
// )
|
||||
.run()
|
||||
.expect("Codegen failed.");
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "packet"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
||||
thiserror = "1.0.37"
|
||||
@@ -1,25 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("the buffer is too small")]
|
||||
SmallBuffer,
|
||||
|
||||
#[error("the packet is invalid")]
|
||||
InvalidPacket,
|
||||
#[error("Unimplemented")]
|
||||
Unimplemented,
|
||||
// #[error("the vaue is invalid for the field")]
|
||||
// InvalidValue,
|
||||
//
|
||||
// #[error("the value has already been defined")]
|
||||
// AlreadyDefined,
|
||||
//
|
||||
// #[error(transparent)]
|
||||
// Io(#[from] io::Error),
|
||||
//
|
||||
// #[error(transparent)]
|
||||
// Nul(#[from] ffi::NulError),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -1,174 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
use crate::cal_checksum;
|
||||
use crate::error::*;
|
||||
/// icmp 协议
|
||||
/* https://www.rfc-editor.org/rfc/rfc792
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 不同Type和Code有不同含义 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 数据体 不同Type和Code有不同含义 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
*/
|
||||
use crate::icmp::{Code, Kind};
|
||||
use crate::ip::ipv4::packet::IpV4Packet;
|
||||
|
||||
pub struct IcmpPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IcmpPacket<B> {
|
||||
pub fn unchecked(buffer: B) -> Self {
|
||||
Self { buffer }
|
||||
}
|
||||
pub fn new(buffer: B) -> Result<Self> {
|
||||
if buffer.as_ref().len() < 8 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
let packet = Self::unchecked(buffer);
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> IcmpPacket<B> {
|
||||
pub fn set_kind(&mut self, kind: Kind) {
|
||||
self.buffer.as_mut()[0] = kind.into();
|
||||
}
|
||||
pub fn update_checksum(&mut self) {
|
||||
self.buffer.as_mut()[2..4].copy_from_slice(&[0, 0]);
|
||||
let checksum = cal_checksum(self.buffer.as_ref());
|
||||
self.buffer.as_mut()[2..4].copy_from_slice(&checksum.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IcmpPacket<B> {
|
||||
pub fn kind(&self) -> Kind {
|
||||
Kind::from(self.buffer.as_ref()[0])
|
||||
}
|
||||
pub fn code(&self) -> Code {
|
||||
Code::from(self.kind(), self.buffer.as_ref()[1])
|
||||
}
|
||||
pub fn checksum(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[2..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.checksum() == 0 || cal_checksum(self.buffer.as_ref()) == 0
|
||||
}
|
||||
pub fn header_other(&self) -> HeaderOther {
|
||||
match self.kind() {
|
||||
Kind::EchoReply
|
||||
| Kind::EchoRequest
|
||||
| Kind::TimestampRequest
|
||||
| Kind::TimestampReply
|
||||
| Kind::InformationRequest
|
||||
| Kind::InformationReply => {
|
||||
let ide = (&self.buffer.as_ref()[4..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap();
|
||||
let seq = (&self.buffer.as_ref()[6..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap();
|
||||
HeaderOther::Identifier(ide, seq)
|
||||
}
|
||||
Kind::DestinationUnreachable | Kind::TimeExceeded | Kind::SourceQuench => {
|
||||
let bytes = self.buffer.as_ref();
|
||||
HeaderOther::Unused(bytes[4], bytes[5], bytes[6], bytes[7])
|
||||
}
|
||||
Kind::Redirect => {
|
||||
let bytes = self.buffer.as_ref();
|
||||
HeaderOther::Address(bytes[4], bytes[5], bytes[6], bytes[7])
|
||||
}
|
||||
Kind::ParameterProblem => HeaderOther::Pointer(self.buffer.as_ref()[4]),
|
||||
_ => {
|
||||
let bytes = self.buffer.as_ref();
|
||||
HeaderOther::UnKnown(bytes[4], bytes[5], bytes[6], bytes[7])
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[8..]
|
||||
}
|
||||
pub fn description(&self) -> Description<&[u8]> {
|
||||
use std::io::Cursor;
|
||||
match self.kind() {
|
||||
Kind::DestinationUnreachable
|
||||
| Kind::TimeExceeded
|
||||
| Kind::ParameterProblem
|
||||
| Kind::SourceQuench
|
||||
| Kind::Redirect => match IpV4Packet::new(self.payload()) {
|
||||
Ok(d) => Description::Ip(d),
|
||||
Err(_) => Description::Other(self.payload()),
|
||||
},
|
||||
Kind::TimestampRequest | Kind::TimestampReply => {
|
||||
let mut buffer = Cursor::new(self.payload());
|
||||
Description::Timestamp(
|
||||
buffer.read_u32::<BigEndian>().unwrap(),
|
||||
buffer.read_u32::<BigEndian>().unwrap(),
|
||||
buffer.read_u32::<BigEndian>().unwrap(),
|
||||
)
|
||||
}
|
||||
_ => Description::Other(self.payload()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for IcmpPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(if self.is_valid() {
|
||||
"icmp::Packet"
|
||||
} else {
|
||||
"icmp::Packet!"
|
||||
})
|
||||
.field("kind", &self.kind())
|
||||
.field("code", &self.code())
|
||||
.field("checksum", &self.checksum())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HeaderOther {
|
||||
/// 全零
|
||||
Unused(u8, u8, u8, u8),
|
||||
/// If code = 0, identifies the octet where an error was detected.
|
||||
Pointer(u8),
|
||||
/// Address of the gateway to which traffic for the network specified
|
||||
/// in the internet destination network field of the original
|
||||
/// datagram's data should be sent.
|
||||
Address(u8, u8, u8, u8),
|
||||
/// Identifier | Sequence Number
|
||||
Identifier(u16, u16),
|
||||
UnKnown(u8, u8, u8, u8),
|
||||
}
|
||||
|
||||
pub enum Description<B> {
|
||||
Ip(IpV4Packet<B>),
|
||||
///时间戳 Originate Timestamp,Receive Timestamp,Transmit Timestamp
|
||||
Timestamp(u32, u32, u32),
|
||||
Other(B),
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + std::fmt::Debug> fmt::Debug for Description<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Description::Ip(packet) => f.debug_struct(&format!("{:?}", packet)).finish(),
|
||||
Description::Timestamp(originate, receive, transmit) => f
|
||||
.debug_struct("")
|
||||
.field("originate", originate)
|
||||
.field("receive", receive)
|
||||
.field("transmit", transmit)
|
||||
.finish(),
|
||||
Description::Other(bytes) => f.debug_struct(&format!("{:?}", bytes)).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
pub mod icmp;
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Kind {
|
||||
/// ping应答,type=0
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Identifier | Sequence Number |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Data ...
|
||||
+-+-+-+-+-
|
||||
*/
|
||||
EchoReply,
|
||||
/// 目的地不可达,差错报文的一种,路由器收到一个不能转发的数据报,会向源地址返回这个报文,type=3
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unused |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Internet Header + 64 bits of Original Data Datagram |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
DestinationUnreachable,
|
||||
/// 源抑制报文,用于防止接收端缓存溢出,接收设备发送这个来请求源设备降低发送速度,type=4
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unused |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Internet Header + 64 bits of Original Data Datagram |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
SourceQuench,
|
||||
/// 重定向报文,当路由器接收包的接口正好是去往目的地的出口时,会向源地址发送重定向报文,告知源直接将数据发往自己的下一跳,type=5
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Gateway Internet Address |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Internet Header + 64 bits of Original Data Datagram |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
Redirect,
|
||||
/// ping请求,type=8
|
||||
EchoRequest,
|
||||
/// 路由器通告,type=9,
|
||||
RouterAdvertisement,
|
||||
/// 路由器请求,type=10
|
||||
RouterSolicitation,
|
||||
/// 报文ttl为0后,路由器会向源发送此报文,type=11
|
||||
/// Tracert工作原理:
|
||||
/// 首先向目的地发送ttl=1的包,下一跳路由器收到后ttl-1,此时ttl=0,将向源发送 ICMP time exceeded
|
||||
/// 再发送ttl=2的包,以此类推,直到目标主机接收到改包,此时不会回复ICMP time exceeded,代表已经探测到目的地
|
||||
/*
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unused |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Internet Header + 64 bits of Original Data Datagram |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
TimeExceeded,
|
||||
/// 参数错误,数据有误、校验和不对等,type=12
|
||||
/*
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Pointer | unused |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Internet Header + 64 bits of Original Data Datagram |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
注:Pointer指示错误的位置
|
||||
*/
|
||||
ParameterProblem,
|
||||
/// 时间戳请求,type=13
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Identifier | Sequence Number |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Originate Timestamp |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Receive Timestamp |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Transmit Timestamp |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
TimestampRequest,
|
||||
/// 时间戳响应,type=14
|
||||
TimestampReply,
|
||||
/// 信息请求,type=15
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Identifier | Sequence Number |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
InformationRequest,
|
||||
/// 信息响应,type=16
|
||||
InformationReply,
|
||||
/// 地址掩码请求,type=17
|
||||
AddressMaskRequest,
|
||||
/// 地址掩码应答,type=18
|
||||
AddressMaskReply,
|
||||
///
|
||||
TraceRoute,
|
||||
///
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Kind {
|
||||
fn from(value: u8) -> Kind {
|
||||
use self::Kind::*;
|
||||
|
||||
match value {
|
||||
0 => EchoReply,
|
||||
3 => DestinationUnreachable,
|
||||
4 => SourceQuench,
|
||||
5 => Redirect,
|
||||
8 => EchoRequest,
|
||||
9 => RouterAdvertisement,
|
||||
10 => RouterSolicitation,
|
||||
11 => TimeExceeded,
|
||||
12 => ParameterProblem,
|
||||
13 => TimestampRequest,
|
||||
14 => TimestampReply,
|
||||
15 => InformationRequest,
|
||||
16 => InformationReply,
|
||||
17 => AddressMaskRequest,
|
||||
18 => AddressMaskReply,
|
||||
30 => TraceRoute,
|
||||
v => Unknown(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Kind {
|
||||
fn into(self) -> u8 {
|
||||
use self::Kind::*;
|
||||
match self {
|
||||
EchoReply => 0,
|
||||
DestinationUnreachable => 3,
|
||||
SourceQuench => 4,
|
||||
Redirect => 5,
|
||||
EchoRequest => 8,
|
||||
RouterAdvertisement => 9,
|
||||
RouterSolicitation => 10,
|
||||
TimeExceeded => 11,
|
||||
ParameterProblem => 12,
|
||||
TimestampRequest => 13,
|
||||
TimestampReply => 14,
|
||||
InformationRequest => 15,
|
||||
InformationReply => 16,
|
||||
AddressMaskRequest => 17,
|
||||
AddressMaskReply => 18,
|
||||
TraceRoute => 30,
|
||||
Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Code {
|
||||
DestinationUnreachable(DestinationUnreachable),
|
||||
Redirect(Redirect),
|
||||
ParameterProblem(ParameterProblem),
|
||||
Other(u8),
|
||||
}
|
||||
|
||||
impl Code {
|
||||
pub fn from(kind: Kind, code: u8) -> Code {
|
||||
match kind {
|
||||
Kind::DestinationUnreachable => {
|
||||
Code::DestinationUnreachable(DestinationUnreachable::from(code))
|
||||
}
|
||||
Kind::Redirect => Code::Redirect(Redirect::from(code)),
|
||||
Kind::ParameterProblem => Code::ParameterProblem(ParameterProblem::from(code)),
|
||||
_ => Code::Other(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum DestinationUnreachable {
|
||||
/// 网络不可达
|
||||
DestinationNetworkUnreachable,
|
||||
/// 主机不可达
|
||||
DestinationHostUnreachable,
|
||||
/// 协议不可达
|
||||
DestinationProtocolUnreachable,
|
||||
/// 端口不可达
|
||||
DestinationPortUnreachable,
|
||||
/// 需要进行分片但设置不分片比特
|
||||
FragmentationRequired,
|
||||
/// 源站选路失败
|
||||
SourceRouteFailed,
|
||||
/// 目的网络未知
|
||||
DestinationNetworkUnknown,
|
||||
/// 目的主机未知
|
||||
DestinationHostUnknown,
|
||||
/// 源主机被隔离(作废不用)
|
||||
SourceHostIsolated,
|
||||
/// 目的网络被强制禁止
|
||||
NetworkAdministrativelyProhibited,
|
||||
/// 目的主机被强制禁止
|
||||
HostAdministrativelyProhibited,
|
||||
/// 由于服务类型TOS,网络不可达
|
||||
NetworkUnreachableForTos,
|
||||
/// 由于服务类型TOS,主机不可达
|
||||
HostUnreachableForTos,
|
||||
/// 由于过滤,通信被强制禁止
|
||||
CommunicationAdministrativelyProhibited,
|
||||
/// 主机越权
|
||||
HostPrecedenceViolation,
|
||||
/// 优先中止生效
|
||||
PrecedentCutoffInEffect,
|
||||
///
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
/// Codes for Redirect Message packets.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Redirect {
|
||||
/// 对网络重定向
|
||||
RedirectDatagramForNetwork,
|
||||
/// 对主机重定向
|
||||
RedirectDatagramForHost,
|
||||
/// 对服务类型和网络重定向
|
||||
RedirectDatagramForTosAndNetwork,
|
||||
/// 对服务类型和主机重定向
|
||||
RedirectDatagramForTosAndHost,
|
||||
///
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
/// Codes for TimeExceeded Message packets.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum TimeExceeded {
|
||||
/// TTL超时报文
|
||||
Transit,
|
||||
/// 分片重组超时报文
|
||||
Reassembly,
|
||||
///
|
||||
Unknown(u8),
|
||||
}
|
||||
/// Codes for Parameter Problem packets.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum ParameterProblem {
|
||||
/// 坏的IP首部(包括各种差错)
|
||||
PointerIndicatesError,
|
||||
/// 缺少必需的选项
|
||||
MissingRequiredData,
|
||||
/// 长度错误
|
||||
BadLength,
|
||||
///
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for DestinationUnreachable {
|
||||
fn from(value: u8) -> Self {
|
||||
use self::DestinationUnreachable::*;
|
||||
|
||||
match value {
|
||||
0 => DestinationNetworkUnreachable,
|
||||
1 => DestinationHostUnreachable,
|
||||
2 => DestinationProtocolUnreachable,
|
||||
3 => DestinationPortUnreachable,
|
||||
4 => FragmentationRequired,
|
||||
5 => SourceRouteFailed,
|
||||
6 => DestinationNetworkUnknown,
|
||||
7 => DestinationHostUnknown,
|
||||
8 => SourceHostIsolated,
|
||||
9 => NetworkAdministrativelyProhibited,
|
||||
10 => HostAdministrativelyProhibited,
|
||||
11 => NetworkUnreachableForTos,
|
||||
12 => HostUnreachableForTos,
|
||||
13 => CommunicationAdministrativelyProhibited,
|
||||
14 => HostPrecedenceViolation,
|
||||
15 => PrecedentCutoffInEffect,
|
||||
v => Unknown(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for DestinationUnreachable {
|
||||
fn into(self) -> u8 {
|
||||
use self::DestinationUnreachable::*;
|
||||
|
||||
match self {
|
||||
DestinationNetworkUnreachable => 0,
|
||||
DestinationHostUnreachable => 1,
|
||||
DestinationProtocolUnreachable => 2,
|
||||
DestinationPortUnreachable => 3,
|
||||
FragmentationRequired => 4,
|
||||
SourceRouteFailed => 5,
|
||||
DestinationNetworkUnknown => 6,
|
||||
DestinationHostUnknown => 7,
|
||||
SourceHostIsolated => 8,
|
||||
NetworkAdministrativelyProhibited => 9,
|
||||
HostAdministrativelyProhibited => 10,
|
||||
NetworkUnreachableForTos => 11,
|
||||
HostUnreachableForTos => 12,
|
||||
CommunicationAdministrativelyProhibited => 13,
|
||||
HostPrecedenceViolation => 14,
|
||||
PrecedentCutoffInEffect => 15,
|
||||
Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Redirect {
|
||||
fn from(value: u8) -> Self {
|
||||
use self::Redirect::*;
|
||||
|
||||
match value {
|
||||
0 => RedirectDatagramForNetwork,
|
||||
1 => RedirectDatagramForHost,
|
||||
2 => RedirectDatagramForTosAndNetwork,
|
||||
3 => RedirectDatagramForTosAndHost,
|
||||
v => Unknown(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Redirect {
|
||||
fn into(self) -> u8 {
|
||||
use self::Redirect::*;
|
||||
|
||||
match self {
|
||||
RedirectDatagramForNetwork => 0,
|
||||
RedirectDatagramForHost => 1,
|
||||
RedirectDatagramForTosAndNetwork => 2,
|
||||
RedirectDatagramForTosAndHost => 3,
|
||||
Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TimeExceeded {
|
||||
fn from(value: u8) -> Self {
|
||||
use self::TimeExceeded::*;
|
||||
|
||||
match value {
|
||||
0 => Transit,
|
||||
1 => Reassembly,
|
||||
v => Unknown(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for TimeExceeded {
|
||||
fn into(self) -> u8 {
|
||||
use self::TimeExceeded::*;
|
||||
|
||||
match self {
|
||||
Transit => 0,
|
||||
Reassembly => 1,
|
||||
Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ParameterProblem {
|
||||
fn from(value: u8) -> Self {
|
||||
use self::ParameterProblem::*;
|
||||
|
||||
match value {
|
||||
0 => PointerIndicatesError,
|
||||
1 => MissingRequiredData,
|
||||
2 => BadLength,
|
||||
v => Unknown(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for ParameterProblem {
|
||||
fn into(self) -> u8 {
|
||||
use self::ParameterProblem::*;
|
||||
|
||||
match self {
|
||||
PointerIndicatesError => 0,
|
||||
MissingRequiredData => 1,
|
||||
BadLength => 2,
|
||||
Unknown(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod packet;
|
||||
pub mod protocol;
|
||||
@@ -1,250 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
use crate::cal_checksum;
|
||||
use crate::error::*;
|
||||
use crate::ip::ipv4::protocol::Protocol;
|
||||
|
||||
/// ip协议
|
||||
/*
|
||||
RFC: 791 https://www.ietf.org/rfc/rfc791.txt
|
||||
|
||||
0 15 31
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 版本(4) | 头部长度(4) | 服务类型(8) | 总字节数(16) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 标识(16) | 标志(3) | 片偏移(13) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 生存时间(8) | 协议(8) | 头部校验和(16) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 源ip地址(32) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 目的ip地址(32) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 选项 + 填充 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
数据体
|
||||
注:头部长度单位是4字节,所以ip头最长60字节,选项最长40字节,选项填充按4字节对齐
|
||||
*/
|
||||
|
||||
pub struct IpV4Packet<B> {
|
||||
pub buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IpV4Packet<B> {
|
||||
pub fn unchecked(buffer: B) -> Self {
|
||||
Self { buffer }
|
||||
}
|
||||
pub fn new(buffer: B) -> Result<Self> {
|
||||
if buffer.as_ref()[0] >> 4 != 4 {
|
||||
Err(Error::Unimplemented)?
|
||||
}
|
||||
if buffer.as_ref().len() < 20 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
let packet = Self::unchecked(buffer);
|
||||
if packet.buffer.as_ref().len() < packet.header_len() as usize * 4 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IpV4Packet<B> {
|
||||
pub fn header(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[..(self.header_len() as usize * 4)]
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[(self.header_len() as usize * 4)..]
|
||||
// match self.protocol() {
|
||||
// Protocol::Udp => {
|
||||
// let udp = UdpPacket::new(IpAddr::V4(self.source_ip()),
|
||||
// IpAddr::V4(self.destination_ip()),
|
||||
// &self.buffer.as_ref()[(self.header_len() as usize * 4)..])?;
|
||||
// Ok(crate::IpUpperLayer::UDP(udp))
|
||||
// }
|
||||
// _ => {
|
||||
// Ok(crate::IpUpperLayer::Unknown(self.buffer.as_ref()));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> IpV4Packet<B> {
|
||||
pub fn header_mut(&mut self) -> &mut [u8] {
|
||||
let len = self.header_len() as usize * 4;
|
||||
&mut self.buffer.as_mut()[..len]
|
||||
}
|
||||
pub fn payload_mut(&mut self) -> &mut [u8] {
|
||||
let len = self.header_len() as usize * 4;
|
||||
&mut self.buffer.as_mut()[len..]
|
||||
}
|
||||
|
||||
pub fn set_source_ip(&mut self, value: Ipv4Addr) {
|
||||
self.header_mut()[12..16].copy_from_slice(&value.octets());
|
||||
}
|
||||
pub fn set_destination_ip(&mut self, value: Ipv4Addr) {
|
||||
self.header_mut()[16..20].copy_from_slice(&value.octets());
|
||||
}
|
||||
fn set_checksum(&mut self, value: u16) {
|
||||
self.header_mut()[10..12].copy_from_slice(&value.to_be_bytes())
|
||||
}
|
||||
/// 更新校验和
|
||||
pub fn update_checksum(&mut self) {
|
||||
//先将校验和置0
|
||||
self.set_checksum(0);
|
||||
self.set_checksum(cal_checksum(self.header()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IpV4Packet<B> {
|
||||
/// 版本号,ipv4的为4
|
||||
pub fn version(&self) -> u8 {
|
||||
self.buffer.as_ref()[0] >> 4
|
||||
}
|
||||
|
||||
/// 头部长度,以4字节为单位
|
||||
pub fn header_len(&self) -> u8 {
|
||||
self.buffer.as_ref()[0] & 0b1111
|
||||
}
|
||||
|
||||
/// 差异化服务编码点
|
||||
///
|
||||
/// 类别(3)+丢失概率(2)+用途(1)
|
||||
///
|
||||
///
|
||||
/// 类别子字段值 | 名称
|
||||
/// ---|:---
|
||||
/// 000 | 常规(Routine)
|
||||
/// 001 | 优先(Priority)
|
||||
/// 010 | 立即(Immediate)
|
||||
/// 011 | 瞬间(Flash)
|
||||
/// 100 | 瞬间覆盖(Flash Override)
|
||||
/// 101 | 严重(CRITIC/ECP)
|
||||
/// 110 | 网间控制(Internetwork Control)
|
||||
/// 111 | 网络控制(Network Control)
|
||||
///
|
||||
///
|
||||
/// 参考:https://www.modb.pro/db/477116
|
||||
pub fn dscp(&self) -> u8 {
|
||||
self.buffer.as_ref()[1] >> 2
|
||||
}
|
||||
|
||||
/// 显示拥塞 00:发送主机不支持ECN 01或者10:发送主机支持ECN 11:路由器正在经历拥塞
|
||||
pub fn ecn(&self) -> u8 {
|
||||
self.buffer.as_ref()[1] & 0b11
|
||||
}
|
||||
|
||||
/// ip报总字节数
|
||||
pub fn length(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[2..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 标识. ip报文在数据链路层可能会被拆分,同一报文的不同分组标识字段相同
|
||||
pub fn id(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[4..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 标志 3位.
|
||||
/// 第1位没有使用
|
||||
/// 第2位表示不分段位(DF)
|
||||
/// 0:允许数据报分段
|
||||
/// 1:数据报不能分段
|
||||
/// 置1之后路由器不能对其分段处理,如果超过MTU值则路由器不能对其转发将其丢弃,并向源点发送错误消息
|
||||
/// 第3位表示更多段位
|
||||
/// 0:数据包后面没有包,该包为最后的包
|
||||
/// 1:数据包后面有更多的包
|
||||
pub fn flags(&self) -> u8 {
|
||||
self.buffer.as_ref()[6] >> 5
|
||||
}
|
||||
|
||||
/// 片偏移 13位.
|
||||
/// 以字节为单位,用于指明分段起始点相对于包头起始点的偏移量
|
||||
/// 由于分段到达时可能错序,所以分段的偏移字段可以使接收者按照正确的顺序重组数据包
|
||||
pub fn offset(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[6..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
& 0x1fff
|
||||
}
|
||||
|
||||
/// 生存时间.
|
||||
/// 每一跳 减1 到0了则会被丢弃
|
||||
pub fn ttl(&self) -> u8 {
|
||||
self.buffer.as_ref()[8]
|
||||
}
|
||||
|
||||
/// 协议.
|
||||
pub fn protocol(&self) -> Protocol {
|
||||
self.buffer.as_ref()[9].into()
|
||||
}
|
||||
|
||||
/// 首部校验和
|
||||
pub fn checksum(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[10..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 验证校验和
|
||||
///
|
||||
/// TCP/IP协议栈不会自己计算校验和,而是简单地将一个空的校验和字段(零或随机填充)交给网卡硬件。
|
||||
/// 所以抓到发出去的包校验和可能是错误的
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.checksum() == 0 || cal_checksum(self.header()) == 0
|
||||
}
|
||||
/// 源ip.
|
||||
pub fn source_ip(&self) -> Ipv4Addr {
|
||||
Ipv4Addr::new(
|
||||
self.buffer.as_ref()[12],
|
||||
self.buffer.as_ref()[13],
|
||||
self.buffer.as_ref()[14],
|
||||
self.buffer.as_ref()[15],
|
||||
)
|
||||
}
|
||||
|
||||
/// 目标ip.
|
||||
pub fn destination_ip(&self) -> Ipv4Addr {
|
||||
Ipv4Addr::new(
|
||||
self.buffer.as_ref()[16],
|
||||
self.buffer.as_ref()[17],
|
||||
self.buffer.as_ref()[18],
|
||||
self.buffer.as_ref()[19],
|
||||
)
|
||||
}
|
||||
|
||||
/// 选项.
|
||||
pub fn options(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[20..(self.header_len() as usize * 4)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for IpV4Packet<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ip::v4::Packet")
|
||||
.field("version", &self.version())
|
||||
.field("header_len", &self.header_len())
|
||||
.field("dscp", &self.dscp())
|
||||
.field("ecn", &self.ecn())
|
||||
.field("length", &self.length())
|
||||
.field("id", &self.id())
|
||||
.field("flags", &self.flags())
|
||||
.field("offset", &self.offset())
|
||||
.field("ttl", &self.ttl())
|
||||
.field("protocol", &self.protocol())
|
||||
.field("checksum", &self.checksum())
|
||||
.field("is_valid", &self.is_valid())
|
||||
.field("source", &self.source_ip())
|
||||
.field("destination", &self.destination_ip())
|
||||
.field("options", &self.options())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,742 +0,0 @@
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Protocol {
|
||||
///
|
||||
Hopopt,
|
||||
|
||||
///
|
||||
Icmp,
|
||||
|
||||
///
|
||||
Igmp,
|
||||
|
||||
///
|
||||
Ggp,
|
||||
|
||||
///
|
||||
Ipv4,
|
||||
|
||||
///
|
||||
St,
|
||||
|
||||
///
|
||||
Tcp,
|
||||
|
||||
///
|
||||
Cbt,
|
||||
|
||||
///
|
||||
Egp,
|
||||
|
||||
///
|
||||
Igp,
|
||||
|
||||
///
|
||||
BbnRccMon,
|
||||
|
||||
///
|
||||
NvpII,
|
||||
|
||||
///
|
||||
Pup,
|
||||
|
||||
///
|
||||
Argus,
|
||||
|
||||
///
|
||||
Emcon,
|
||||
|
||||
///
|
||||
Xnet,
|
||||
|
||||
///
|
||||
Chaos,
|
||||
|
||||
///
|
||||
Udp,
|
||||
|
||||
///
|
||||
Mux,
|
||||
|
||||
///
|
||||
DcnMeas,
|
||||
|
||||
///
|
||||
Hmp,
|
||||
|
||||
///
|
||||
Prm,
|
||||
|
||||
///
|
||||
XnsIdp,
|
||||
|
||||
///
|
||||
Trunk1,
|
||||
|
||||
///
|
||||
Trunk2,
|
||||
|
||||
///
|
||||
Leaf1,
|
||||
|
||||
///
|
||||
Leaf2,
|
||||
|
||||
///
|
||||
Rdp,
|
||||
|
||||
///
|
||||
Irtp,
|
||||
|
||||
///
|
||||
IsoTp4,
|
||||
|
||||
///
|
||||
Netblt,
|
||||
|
||||
///
|
||||
MfeNsp,
|
||||
|
||||
///
|
||||
MeritInp,
|
||||
|
||||
///
|
||||
Dccp,
|
||||
|
||||
///
|
||||
ThreePc,
|
||||
|
||||
///
|
||||
Idpr,
|
||||
|
||||
///
|
||||
Xtp,
|
||||
|
||||
///
|
||||
Ddp,
|
||||
|
||||
///
|
||||
IdprCmtp,
|
||||
|
||||
///
|
||||
TpPlusPlus,
|
||||
|
||||
///
|
||||
Il,
|
||||
|
||||
///
|
||||
Ipv6,
|
||||
|
||||
///
|
||||
Sdrp,
|
||||
|
||||
///
|
||||
Ipv6Route,
|
||||
|
||||
///
|
||||
Ipv6Frag,
|
||||
|
||||
///
|
||||
Idrp,
|
||||
|
||||
///
|
||||
Rsvp,
|
||||
|
||||
///
|
||||
Gre,
|
||||
|
||||
///
|
||||
Dsr,
|
||||
|
||||
///
|
||||
Bna,
|
||||
|
||||
///
|
||||
Esp,
|
||||
|
||||
///
|
||||
Ah,
|
||||
|
||||
///
|
||||
INlsp,
|
||||
|
||||
///
|
||||
Swipe,
|
||||
|
||||
///
|
||||
Narp,
|
||||
|
||||
///
|
||||
Mobile,
|
||||
|
||||
///
|
||||
Tlsp,
|
||||
|
||||
///
|
||||
Skip,
|
||||
|
||||
///
|
||||
Ipv6Icmp,
|
||||
|
||||
///
|
||||
Ipv6NoNxt,
|
||||
|
||||
///
|
||||
Ipv6Opts,
|
||||
|
||||
///
|
||||
HostInternal,
|
||||
|
||||
///
|
||||
Cftp,
|
||||
|
||||
///
|
||||
LocalNetwork,
|
||||
|
||||
///
|
||||
SatExpak,
|
||||
|
||||
///
|
||||
Kryptolan,
|
||||
|
||||
///
|
||||
Rvd,
|
||||
|
||||
///
|
||||
Ippc,
|
||||
|
||||
///
|
||||
DistributedFs,
|
||||
|
||||
///
|
||||
SatMon,
|
||||
|
||||
///
|
||||
Visa,
|
||||
|
||||
///
|
||||
Ipcv,
|
||||
|
||||
///
|
||||
Cpnx,
|
||||
|
||||
///
|
||||
Cphb,
|
||||
|
||||
///
|
||||
Wsn,
|
||||
|
||||
///
|
||||
Pvp,
|
||||
|
||||
///
|
||||
BrSatMon,
|
||||
|
||||
///
|
||||
SunNd,
|
||||
|
||||
///
|
||||
WbMon,
|
||||
|
||||
///
|
||||
WbExpak,
|
||||
|
||||
///
|
||||
IsoIp,
|
||||
|
||||
///
|
||||
Vmtp,
|
||||
|
||||
///
|
||||
SecureVmtp,
|
||||
|
||||
///
|
||||
Vines,
|
||||
|
||||
///
|
||||
TtpOrIptm,
|
||||
|
||||
///
|
||||
NsfnetIgp,
|
||||
|
||||
///
|
||||
Dgp,
|
||||
|
||||
///
|
||||
Tcf,
|
||||
|
||||
///
|
||||
Eigrp,
|
||||
|
||||
///
|
||||
OspfigP,
|
||||
|
||||
///
|
||||
SpriteRpc,
|
||||
|
||||
///
|
||||
Larp,
|
||||
|
||||
///
|
||||
Mtp,
|
||||
|
||||
///
|
||||
Ax25,
|
||||
|
||||
///
|
||||
IpIp,
|
||||
|
||||
///
|
||||
Micp,
|
||||
|
||||
///
|
||||
SccSp,
|
||||
|
||||
///
|
||||
Etherip,
|
||||
|
||||
///
|
||||
Encap,
|
||||
|
||||
///
|
||||
PrivEncryption,
|
||||
|
||||
///
|
||||
Gmtp,
|
||||
|
||||
///
|
||||
Ifmp,
|
||||
|
||||
///
|
||||
Pnni,
|
||||
|
||||
///
|
||||
Pim,
|
||||
|
||||
///
|
||||
Aris,
|
||||
|
||||
///
|
||||
Scps,
|
||||
|
||||
///
|
||||
Qnx,
|
||||
|
||||
///
|
||||
AN,
|
||||
|
||||
///
|
||||
IpComp,
|
||||
|
||||
///
|
||||
Snp,
|
||||
|
||||
///
|
||||
CompaqPeer,
|
||||
|
||||
///
|
||||
IpxInIp,
|
||||
|
||||
///
|
||||
Vrrp,
|
||||
|
||||
///
|
||||
Pgm,
|
||||
|
||||
///
|
||||
ZeroHop,
|
||||
|
||||
///
|
||||
L2tp,
|
||||
|
||||
///
|
||||
Ddx,
|
||||
|
||||
///
|
||||
Iatp,
|
||||
|
||||
///
|
||||
Stp,
|
||||
|
||||
///
|
||||
Srp,
|
||||
|
||||
///
|
||||
Uti,
|
||||
|
||||
///
|
||||
Smp,
|
||||
|
||||
///
|
||||
Sm,
|
||||
|
||||
///
|
||||
Ptp,
|
||||
|
||||
///
|
||||
IsisOverIpv4,
|
||||
|
||||
///
|
||||
Fire,
|
||||
|
||||
///
|
||||
Crtp,
|
||||
|
||||
///
|
||||
Crudp,
|
||||
|
||||
///
|
||||
Sscopmce,
|
||||
|
||||
///
|
||||
Iplt,
|
||||
|
||||
///
|
||||
Sps,
|
||||
|
||||
///
|
||||
Pipe,
|
||||
|
||||
///
|
||||
Sctp,
|
||||
|
||||
///
|
||||
Fc,
|
||||
|
||||
///
|
||||
RsvpE2eIgnore,
|
||||
|
||||
///
|
||||
MobilityHeader,
|
||||
|
||||
///
|
||||
UdpLite,
|
||||
|
||||
///
|
||||
MplsInIp,
|
||||
|
||||
///
|
||||
Manet,
|
||||
|
||||
///
|
||||
Hip,
|
||||
|
||||
///
|
||||
Shim6,
|
||||
|
||||
///
|
||||
Wesp,
|
||||
Rohc,
|
||||
Test1,
|
||||
Test2,
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Protocol {
|
||||
use self::Protocol::*;
|
||||
|
||||
match value {
|
||||
0 => Hopopt,
|
||||
1 => Icmp,
|
||||
2 => Igmp,
|
||||
3 => Ggp,
|
||||
4 => Ipv4,
|
||||
5 => St,
|
||||
6 => Tcp,
|
||||
7 => Cbt,
|
||||
8 => Egp,
|
||||
9 => Igp,
|
||||
10 => BbnRccMon,
|
||||
11 => NvpII,
|
||||
12 => Pup,
|
||||
13 => Argus,
|
||||
14 => Emcon,
|
||||
15 => Xnet,
|
||||
16 => Chaos,
|
||||
17 => Udp,
|
||||
18 => Mux,
|
||||
19 => DcnMeas,
|
||||
20 => Hmp,
|
||||
21 => Prm,
|
||||
22 => XnsIdp,
|
||||
23 => Trunk1,
|
||||
24 => Trunk2,
|
||||
25 => Leaf1,
|
||||
26 => Leaf2,
|
||||
27 => Rdp,
|
||||
28 => Irtp,
|
||||
29 => IsoTp4,
|
||||
30 => Netblt,
|
||||
31 => MfeNsp,
|
||||
32 => MeritInp,
|
||||
33 => Dccp,
|
||||
34 => ThreePc,
|
||||
35 => Idpr,
|
||||
36 => Xtp,
|
||||
37 => Ddp,
|
||||
38 => IdprCmtp,
|
||||
39 => TpPlusPlus,
|
||||
40 => Il,
|
||||
41 => Ipv6,
|
||||
42 => Sdrp,
|
||||
43 => Ipv6Route,
|
||||
44 => Ipv6Frag,
|
||||
45 => Idrp,
|
||||
46 => Rsvp,
|
||||
47 => Gre,
|
||||
48 => Dsr,
|
||||
49 => Bna,
|
||||
50 => Esp,
|
||||
51 => Ah,
|
||||
52 => INlsp,
|
||||
53 => Swipe,
|
||||
54 => Narp,
|
||||
55 => Mobile,
|
||||
56 => Tlsp,
|
||||
57 => Skip,
|
||||
58 => Ipv6Icmp,
|
||||
59 => Ipv6NoNxt,
|
||||
60 => Ipv6Opts,
|
||||
61 => HostInternal,
|
||||
62 => Cftp,
|
||||
63 => LocalNetwork,
|
||||
64 => SatExpak,
|
||||
65 => Kryptolan,
|
||||
66 => Rvd,
|
||||
67 => Ippc,
|
||||
68 => DistributedFs,
|
||||
69 => SatMon,
|
||||
70 => Visa,
|
||||
71 => Ipcv,
|
||||
72 => Cpnx,
|
||||
73 => Cphb,
|
||||
74 => Wsn,
|
||||
75 => Pvp,
|
||||
76 => BrSatMon,
|
||||
77 => SunNd,
|
||||
78 => WbMon,
|
||||
79 => WbExpak,
|
||||
80 => IsoIp,
|
||||
81 => Vmtp,
|
||||
82 => SecureVmtp,
|
||||
83 => Vines,
|
||||
84 => TtpOrIptm,
|
||||
85 => NsfnetIgp,
|
||||
86 => Dgp,
|
||||
87 => Tcf,
|
||||
88 => Eigrp,
|
||||
89 => OspfigP,
|
||||
90 => SpriteRpc,
|
||||
91 => Larp,
|
||||
92 => Mtp,
|
||||
93 => Ax25,
|
||||
94 => IpIp,
|
||||
95 => Micp,
|
||||
96 => SccSp,
|
||||
97 => Etherip,
|
||||
98 => Encap,
|
||||
99 => PrivEncryption,
|
||||
100 => Gmtp,
|
||||
101 => Ifmp,
|
||||
102 => Pnni,
|
||||
103 => Pim,
|
||||
104 => Aris,
|
||||
105 => Scps,
|
||||
106 => Qnx,
|
||||
107 => AN,
|
||||
108 => IpComp,
|
||||
109 => Snp,
|
||||
110 => CompaqPeer,
|
||||
111 => IpxInIp,
|
||||
112 => Vrrp,
|
||||
113 => Pgm,
|
||||
114 => ZeroHop,
|
||||
115 => L2tp,
|
||||
116 => Ddx,
|
||||
117 => Iatp,
|
||||
118 => Stp,
|
||||
119 => Srp,
|
||||
120 => Uti,
|
||||
121 => Smp,
|
||||
122 => Sm,
|
||||
123 => Ptp,
|
||||
124 => IsisOverIpv4,
|
||||
125 => Fire,
|
||||
126 => Crtp,
|
||||
127 => Crudp,
|
||||
128 => Sscopmce,
|
||||
129 => Iplt,
|
||||
130 => Sps,
|
||||
131 => Pipe,
|
||||
132 => Sctp,
|
||||
133 => Fc,
|
||||
134 => RsvpE2eIgnore,
|
||||
135 => MobilityHeader,
|
||||
136 => UdpLite,
|
||||
137 => MplsInIp,
|
||||
138 => Manet,
|
||||
139 => Hip,
|
||||
140 => Shim6,
|
||||
141 => Wesp,
|
||||
142 => Rohc,
|
||||
253 => Test1,
|
||||
254 => Test2,
|
||||
p => Unknown(p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
use self::Protocol::*;
|
||||
|
||||
match self {
|
||||
Hopopt => 0,
|
||||
Icmp => 1,
|
||||
Igmp => 2,
|
||||
Ggp => 3,
|
||||
Ipv4 => 4,
|
||||
St => 5,
|
||||
Tcp => 6,
|
||||
Cbt => 7,
|
||||
Egp => 8,
|
||||
Igp => 9,
|
||||
BbnRccMon => 10,
|
||||
NvpII => 11,
|
||||
Pup => 12,
|
||||
Argus => 13,
|
||||
Emcon => 14,
|
||||
Xnet => 15,
|
||||
Chaos => 16,
|
||||
Udp => 17,
|
||||
Mux => 18,
|
||||
DcnMeas => 19,
|
||||
Hmp => 20,
|
||||
Prm => 21,
|
||||
XnsIdp => 22,
|
||||
Trunk1 => 23,
|
||||
Trunk2 => 24,
|
||||
Leaf1 => 25,
|
||||
Leaf2 => 26,
|
||||
Rdp => 27,
|
||||
Irtp => 28,
|
||||
IsoTp4 => 29,
|
||||
Netblt => 30,
|
||||
MfeNsp => 31,
|
||||
MeritInp => 32,
|
||||
Dccp => 33,
|
||||
ThreePc => 34,
|
||||
Idpr => 35,
|
||||
Xtp => 36,
|
||||
Ddp => 37,
|
||||
IdprCmtp => 38,
|
||||
TpPlusPlus => 39,
|
||||
Il => 40,
|
||||
Ipv6 => 41,
|
||||
Sdrp => 42,
|
||||
Ipv6Route => 43,
|
||||
Ipv6Frag => 44,
|
||||
Idrp => 45,
|
||||
Rsvp => 46,
|
||||
Gre => 47,
|
||||
Dsr => 48,
|
||||
Bna => 49,
|
||||
Esp => 50,
|
||||
Ah => 51,
|
||||
INlsp => 52,
|
||||
Swipe => 53,
|
||||
Narp => 54,
|
||||
Mobile => 55,
|
||||
Tlsp => 56,
|
||||
Skip => 57,
|
||||
Ipv6Icmp => 58,
|
||||
Ipv6NoNxt => 59,
|
||||
Ipv6Opts => 60,
|
||||
HostInternal => 61,
|
||||
Cftp => 62,
|
||||
LocalNetwork => 63,
|
||||
SatExpak => 64,
|
||||
Kryptolan => 65,
|
||||
Rvd => 66,
|
||||
Ippc => 67,
|
||||
DistributedFs => 68,
|
||||
SatMon => 69,
|
||||
Visa => 70,
|
||||
Ipcv => 71,
|
||||
Cpnx => 72,
|
||||
Cphb => 73,
|
||||
Wsn => 74,
|
||||
Pvp => 75,
|
||||
BrSatMon => 76,
|
||||
SunNd => 77,
|
||||
WbMon => 78,
|
||||
WbExpak => 79,
|
||||
IsoIp => 80,
|
||||
Vmtp => 81,
|
||||
SecureVmtp => 82,
|
||||
Vines => 83,
|
||||
TtpOrIptm => 84,
|
||||
NsfnetIgp => 85,
|
||||
Dgp => 86,
|
||||
Tcf => 87,
|
||||
Eigrp => 88,
|
||||
OspfigP => 89,
|
||||
SpriteRpc => 90,
|
||||
Larp => 91,
|
||||
Mtp => 92,
|
||||
Ax25 => 93,
|
||||
IpIp => 94,
|
||||
Micp => 95,
|
||||
SccSp => 96,
|
||||
Etherip => 97,
|
||||
Encap => 98,
|
||||
PrivEncryption => 99,
|
||||
Gmtp => 100,
|
||||
Ifmp => 101,
|
||||
Pnni => 102,
|
||||
Pim => 103,
|
||||
Aris => 104,
|
||||
Scps => 105,
|
||||
Qnx => 106,
|
||||
AN => 107,
|
||||
IpComp => 108,
|
||||
Snp => 109,
|
||||
CompaqPeer => 110,
|
||||
IpxInIp => 111,
|
||||
Vrrp => 112,
|
||||
Pgm => 113,
|
||||
ZeroHop => 114,
|
||||
L2tp => 115,
|
||||
Ddx => 116,
|
||||
Iatp => 117,
|
||||
Stp => 118,
|
||||
Srp => 119,
|
||||
Uti => 120,
|
||||
Smp => 121,
|
||||
Sm => 122,
|
||||
Ptp => 123,
|
||||
IsisOverIpv4 => 124,
|
||||
Fire => 125,
|
||||
Crtp => 126,
|
||||
Crudp => 127,
|
||||
Sscopmce => 128,
|
||||
Iplt => 129,
|
||||
Sps => 130,
|
||||
Pipe => 131,
|
||||
Sctp => 132,
|
||||
Fc => 133,
|
||||
RsvpE2eIgnore => 134,
|
||||
MobilityHeader => 135,
|
||||
UdpLite => 136,
|
||||
MplsInIp => 137,
|
||||
Manet => 138,
|
||||
Hip => 139,
|
||||
Shim6 => 140,
|
||||
Wesp => 141,
|
||||
Rohc => 142,
|
||||
Test1 => 253,
|
||||
Test2 => 254,
|
||||
Unknown(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use ipv4::packet::IpV4Packet;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
pub mod ipv4;
|
||||
|
||||
pub enum IpPacket<B> {
|
||||
V4(IpV4Packet<B>),
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> IpPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<Self> {
|
||||
match buffer.as_ref()[0] >> 4 {
|
||||
4 => Ok(IpPacket::V4(IpV4Packet::new(buffer)?)),
|
||||
_ => Err(Error::InvalidPacket),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use byteorder::BigEndian;
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
pub mod error;
|
||||
pub mod icmp;
|
||||
pub mod ip;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
|
||||
// pub enum IpUpperLayer<B> {
|
||||
// UDP(UdpPacket<B>),
|
||||
// Unknown(B),
|
||||
// }
|
||||
//
|
||||
// impl<B: AsRef<[u8]>> fmt::Debug for IpUpperLayer<B> {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// match self {
|
||||
// IpUpperLayer::UDP(p) => {
|
||||
// f.debug_struct("udp::Packet")
|
||||
// .field("data", p).finish()
|
||||
// }
|
||||
// IpUpperLayer::Unknown(p) => {
|
||||
// f.debug_struct("Unknown")
|
||||
// .field("data", &p.as_ref()).finish()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// https://datatracker.ietf.org/doc/html/rfc1071 4.1节
|
||||
///
|
||||
/// 计算校验和,各协议都是通用的
|
||||
/// 计算:
|
||||
/// 首先将校验和置0,然后对首部每个16位数进行二进制反码求和,
|
||||
/// 得到校验和之后,持续取高16位加到低16位,直到高16位全为0
|
||||
/// 最后取反
|
||||
///
|
||||
/// 校验:
|
||||
/// 在已有校验和的情况下,再计算校验和,正确的数据计算得到的值为0
|
||||
/*
|
||||
unsigned short getChecksum(unsigned short * iphead, int count)
|
||||
{
|
||||
unsigned long int sum = 0;
|
||||
unsigned short checksum = 0;
|
||||
|
||||
printf("\nStarting adress: %p\n", iphead);
|
||||
|
||||
while(count > 1) {
|
||||
sum += * (unsigned short *) (iphead);
|
||||
count -=2;
|
||||
printf("a: %p, content is: %d, new sum: %ld\n", iphead, (unsigned short) *(iphead), sum);
|
||||
iphead++;
|
||||
}
|
||||
|
||||
if(count > 0) {
|
||||
sum += * (unsigned short *) (iphead);
|
||||
}
|
||||
|
||||
while(sum >> 16) {
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
}
|
||||
|
||||
checksum = ~sum;
|
||||
|
||||
return checksum;
|
||||
}
|
||||
*/
|
||||
pub fn cal_checksum(buffer: &[u8]) -> u16 {
|
||||
use std::io::Cursor;
|
||||
let mut sum = 0;
|
||||
let length = buffer.len();
|
||||
let mut buffer = Cursor::new(buffer);
|
||||
while let Ok(value) = buffer.read_u16::<BigEndian>() {
|
||||
sum += u32::from(value);
|
||||
}
|
||||
if length & 1 == 1 {
|
||||
//奇数,说明还有一位,不足的补0
|
||||
sum += u32c(buffer.read_u8().unwrap(), 0);
|
||||
}
|
||||
while sum >> 16 != 0 {
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
}
|
||||
!sum as u16
|
||||
}
|
||||
|
||||
/// ipv4上层协议校验和计算方式
|
||||
/// ipv4 udp伪首部 用于参与计算首部校验和
|
||||
/*
|
||||
0 7 8 15 16 23 24 31
|
||||
+--------+--------+--------+--------+
|
||||
| source address |
|
||||
+--------+--------+--------+--------+
|
||||
| destination address |
|
||||
+--------+--------+--------+--------+
|
||||
| zero |protocol| length |
|
||||
+--------+--------+--------+--------+
|
||||
*/
|
||||
pub fn ipv4_cal_checksum(
|
||||
buffer: &[u8],
|
||||
src_ip: &Ipv4Addr,
|
||||
dest_ip: &Ipv4Addr,
|
||||
protocol: u8,
|
||||
length: u16,
|
||||
) -> u16 {
|
||||
use std::io::Cursor;
|
||||
let mut sum = 0;
|
||||
let src_ip = src_ip.octets();
|
||||
sum += u32c(src_ip[0], src_ip[1]);
|
||||
sum += u32c(src_ip[2], src_ip[3]);
|
||||
let dest_ip = dest_ip.octets();
|
||||
sum += u32c(dest_ip[0], dest_ip[1]);
|
||||
sum += u32c(dest_ip[2], dest_ip[3]);
|
||||
sum += u32c(0, protocol);
|
||||
sum += length as u32;
|
||||
let mut buffer = Cursor::new(buffer);
|
||||
while let Ok(value) = buffer.read_u16::<BigEndian>() {
|
||||
sum += u32::from(value);
|
||||
}
|
||||
if length & 1 == 1 {
|
||||
//奇数,说明还有一位
|
||||
sum += u32c(buffer.read_u8().unwrap(), 0);
|
||||
}
|
||||
while sum >> 16 != 0 {
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
}
|
||||
!sum as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn u32c(x: u8, y: u8) -> u32 {
|
||||
((x as u32) << 8) | y as u32
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let sum = cal_checksum(&[255, 255]);
|
||||
println!("{:?}", sum);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
pub mod tcp;
|
||||
|
||||
pub struct Flags(u8);
|
||||
|
||||
pub const FIN: u8 = 0b0000_0001;
|
||||
pub const SYN: u8 = 0b0000_0010;
|
||||
pub const RST: u8 = 0b0000_0100;
|
||||
pub const PSH: u8 = 0b0000_1000;
|
||||
pub const ACK: u8 = 0b0001_0000;
|
||||
pub const URG: u8 = 0b0010_0000;
|
||||
|
||||
impl fmt::Debug for Flags {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut str = String::with_capacity(22);
|
||||
if self.0 & URG != 0 {
|
||||
str.push_str("URG|");
|
||||
}
|
||||
if self.0 & ACK != 0 {
|
||||
str.push_str("ACK|");
|
||||
}
|
||||
if self.0 & PSH != 0 {
|
||||
str.push_str("PSH|");
|
||||
}
|
||||
if self.0 & RST != 0 {
|
||||
str.push_str("RST|");
|
||||
}
|
||||
if self.0 & SYN != 0 {
|
||||
str.push_str("SYN|");
|
||||
}
|
||||
if self.0 & FIN != 0 {
|
||||
str.push_str("FIN|");
|
||||
}
|
||||
if str.is_empty() {
|
||||
f.debug_struct("NULL").finish()
|
||||
} else {
|
||||
let len = str.len() - 1;
|
||||
f.debug_struct(&str[..len]).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
use crate::error::*;
|
||||
use crate::tcp::Flags;
|
||||
|
||||
/// tcp
|
||||
/*
|
||||
https://www.rfc-editor.org/rfc/rfc793
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Source Port | Destination Port |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Sequence Number |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Acknowledgment Number |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Data | |U|A|P|R|S|F| |
|
||||
| Offset| Reserved |R|C|S|S|Y|I| Window |
|
||||
| | |G|K|H|T|N|N| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Checksum | Urgent Pointer |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Options | Padding |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| data |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Source Port: 16位 源端口
|
||||
Destination Port:16位 目的端口
|
||||
Sequence Number:32位 序列号,如果存在syn标志,则为初始序列号
|
||||
Acknowledgment Number:32位 如果设置了ack标志,这个表示确认收到的序号
|
||||
Data Offset:4位 数据的开始偏移位,单位是4字节
|
||||
Reserved:6位 未使用,全零
|
||||
控制位:6位 从左到右
|
||||
URG: 紧急指针 表示数据要优先处理
|
||||
ACK: 确认位
|
||||
PSH: 推送 要求把数据尽快的交给应用层,不做处理
|
||||
RST: 重置连接
|
||||
SYN: 同步序列号
|
||||
FIN: 结束发送
|
||||
Window: 16位 能接收的数据大小
|
||||
Checksum:16位 校验和,需要加入伪首部
|
||||
Urgent Pointer:16位 紧急指针
|
||||
Options+Padding:32位整数倍,最多40个字节
|
||||
*/
|
||||
pub struct TcpPacket<B> {
|
||||
source_ip: IpAddr,
|
||||
destination_ip: IpAddr,
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TcpPacket<B> {
|
||||
pub fn unchecked(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> TcpPacket<B> {
|
||||
TcpPacket {
|
||||
source_ip,
|
||||
destination_ip,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
pub fn new(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> Result<TcpPacket<B>> {
|
||||
let packet = TcpPacket::unchecked(source_ip, destination_ip, buffer);
|
||||
|
||||
if packet.buffer.as_ref().len() < 20 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
|
||||
if packet.buffer.as_ref().len() < packet.data_offset() as usize * 4 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TcpPacket<B> {
|
||||
/// 源端口
|
||||
pub fn source_port(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[0..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 目标端口
|
||||
pub fn destination_port(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[2..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 序列号
|
||||
pub fn sequence(&self) -> u32 {
|
||||
(&self.buffer.as_ref()[4..])
|
||||
.read_u32::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 确认号
|
||||
pub fn acknowledgment(&self) -> u32 {
|
||||
(&self.buffer.as_ref()[8..])
|
||||
.read_u32::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 数据偏移 4字节为单位
|
||||
pub fn data_offset(&self) -> u8 {
|
||||
self.buffer.as_ref()[12] >> 4
|
||||
}
|
||||
pub fn flags(&self) -> Flags {
|
||||
Flags(self.buffer.as_ref()[13])
|
||||
}
|
||||
pub fn window(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[14..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
pub fn checksum(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[16..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 验证校验和,ipv4中为0表示不使用校验和,ipv6校验和不能为0
|
||||
/// TCP/IP协议栈不会自己计算校验和,而是简单地将一个空的校验和字段(零或随机填充)交给网卡硬件。
|
||||
/// 所以抓到发出去的包校验和可能是错误的
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.checksum() == 0 || self.cal_checksum() == 0
|
||||
}
|
||||
fn cal_checksum(&self) -> u16 {
|
||||
match self.source_ip {
|
||||
IpAddr::V4(src) => {
|
||||
if let IpAddr::V4(dest) = self.destination_ip {
|
||||
return crate::ipv4_cal_checksum(
|
||||
self.buffer.as_ref(),
|
||||
&src,
|
||||
&dest,
|
||||
6,
|
||||
self.buffer.as_ref().len() as u16,
|
||||
);
|
||||
}
|
||||
}
|
||||
IpAddr::V6(_src) => {}
|
||||
}
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn urgent_pointer(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[18..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
pub fn options(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[20..(self.data_offset() as usize * 4)]
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[(self.data_offset() as usize * 4)..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for TcpPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("tcp::Packet")
|
||||
.field("source", &self.source_port())
|
||||
.field("destination", &self.destination_port())
|
||||
.field("sequence", &self.sequence())
|
||||
.field("acknowledgment", &self.acknowledgment())
|
||||
.field("offset", &self.data_offset())
|
||||
.field("flags", &self.flags())
|
||||
.field("window", &self.window())
|
||||
.field("checksum", &self.checksum())
|
||||
.field("is_valid", &self.is_valid())
|
||||
.field("pointer", &self.urgent_pointer())
|
||||
.field("options", &self.options())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod udp;
|
||||
@@ -1,177 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use byteorder::WriteBytesExt;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
/// udp协议
|
||||
///
|
||||
/*
|
||||
RFC 768 https://www.ietf.org/rfc/rfc768.txt
|
||||
|
||||
0 7 8 15 16 23 24 31
|
||||
+--------+--------+--------+--------+
|
||||
| 源端口(16) | 目的端口(16) |
|
||||
+--------+--------+--------+--------+
|
||||
| 长度(16) | 校验和(16) |
|
||||
+--------+--------+--------+--------+
|
||||
|
|
||||
| 载荷 ...
|
||||
+---------------- ...
|
||||
|
||||
注:1.长度包含标头和数据体,以字节为单位
|
||||
2.伪首部和载荷参与校验和的计算,位数不够则补0
|
||||
*/
|
||||
|
||||
/// ipv6 udp伪首部
|
||||
/* https://datatracker.ietf.org/doc/html/rfc2460
|
||||
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ Source Address +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ Destination Address +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Upper-Layer Packet Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| zero | Next Header |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
pub struct UdpPacket<B> {
|
||||
source_ip: IpAddr,
|
||||
destination_ip: IpAddr,
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> UdpPacket<B> {
|
||||
pub fn unchecked(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> UdpPacket<B> {
|
||||
UdpPacket {
|
||||
source_ip,
|
||||
destination_ip,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
pub fn new(source_ip: IpAddr, destination_ip: IpAddr, buffer: B) -> Result<UdpPacket<B>> {
|
||||
if buffer.as_ref().len() < 8 {
|
||||
Err(Error::SmallBuffer)?
|
||||
}
|
||||
let packet = Self::unchecked(source_ip, destination_ip, buffer);
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> UdpPacket<B> {
|
||||
/// 源端口
|
||||
pub fn source_port(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[0..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 目标端口
|
||||
pub fn destination_port(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[2..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// 总字节数
|
||||
pub fn length(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[4..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Checksum of the packet.
|
||||
pub fn checksum(&self) -> u16 {
|
||||
(&self.buffer.as_ref()[6..])
|
||||
.read_u16::<BigEndian>()
|
||||
.unwrap()
|
||||
}
|
||||
/// 验证校验和,ipv4中为0表示不使用校验和,ipv6校验和不能为0
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.checksum() == 0 || self.cal_checksum() == 0
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[8..]
|
||||
}
|
||||
fn cal_checksum(&self) -> u16 {
|
||||
match self.source_ip {
|
||||
IpAddr::V4(src) => {
|
||||
if let IpAddr::V4(dest) = self.destination_ip {
|
||||
return crate::ipv4_cal_checksum(
|
||||
self.buffer.as_ref(),
|
||||
&src,
|
||||
&dest,
|
||||
17,
|
||||
self.length(),
|
||||
);
|
||||
}
|
||||
}
|
||||
IpAddr::V6(_src) => {}
|
||||
}
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> UdpPacket<B> {
|
||||
fn header_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer.as_mut()[..8]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> UdpPacket<B> {
|
||||
/// 设置源端口
|
||||
pub fn set_source_port(&mut self, value: u16) -> &mut Self {
|
||||
Cursor::new(&mut self.header_mut()[0..])
|
||||
.write_u16::<BigEndian>(value)
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置目的端口
|
||||
pub fn set_destination_port(&mut self, value: u16) -> &mut Self {
|
||||
Cursor::new(&mut self.header_mut()[2..])
|
||||
.write_u16::<BigEndian>(value)
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
fn set_checknum(&mut self, value: u16) {
|
||||
Cursor::new(&mut self.header_mut()[6..])
|
||||
.write_u16::<BigEndian>(value)
|
||||
.unwrap();
|
||||
}
|
||||
pub fn update_checknum(&mut self) {
|
||||
//先写0
|
||||
self.set_checknum(0);
|
||||
self.set_checknum(self.cal_checksum());
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for UdpPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("udp::Packet")
|
||||
.field("source", &self.source_port())
|
||||
.field("destination", &self.destination_port())
|
||||
.field("length", &self.length())
|
||||
.field("checksum", &self.checksum())
|
||||
.field("is_valid", &self.is_valid())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
syntax = "proto3";
|
||||
message RegistrationRequest{
|
||||
string token = 1;
|
||||
string mac_address = 2;
|
||||
}
|
||||
|
||||
message RegistrationResponse{
|
||||
fixed32 virtual_ip = 1;
|
||||
fixed32 virtual_gateway = 2;
|
||||
fixed32 virtual_netmask = 3;
|
||||
uint32 epoch = 4;
|
||||
repeated fixed32 virtual_ip_list = 5;
|
||||
fixed32 public_ip = 6;
|
||||
uint32 public_port = 7;
|
||||
}
|
||||
|
||||
message DeviceList{
|
||||
uint32 epoch = 1;
|
||||
repeated fixed32 virtual_ip_list = 2;
|
||||
}
|
||||
|
||||
message Punch{
|
||||
fixed32 virtual_ip = 1;
|
||||
repeated fixed32 public_ip_list = 2;
|
||||
uint32 public_port = 3;
|
||||
uint32 public_port_range = 4;
|
||||
NatType nat_type = 5;
|
||||
bool reply = 6;
|
||||
Step step = 7;
|
||||
}
|
||||
enum NatType{
|
||||
Symmetric = 0;
|
||||
Cone = 1;
|
||||
}
|
||||
enum Step{
|
||||
Step1 = 0;
|
||||
Step2 = 1;
|
||||
Step3 = 2;
|
||||
Step4 = 3;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "tun"
|
||||
version = "0.5.4"
|
||||
edition = "2018"
|
||||
|
||||
authors = ["meh. <[email protected]>"]
|
||||
license = "WTFPL"
|
||||
|
||||
description = "TUN device creation and handling."
|
||||
repository = "https://github.com/meh/rust-tun"
|
||||
keywords = ["tun", "network", "tunnel", "bindings"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
thiserror = "1"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "android"))'.dependencies]
|
||||
tokio = { version = "1", features = ["net", "macros"], optional = true }
|
||||
tokio-util = { version = "0.6", features = ["codec"], optional = true }
|
||||
bytes = { version = "1", optional = true }
|
||||
byteorder = { version = "1", optional = true }
|
||||
# This is only for the `ready` macro.
|
||||
futures-core = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
|
||||
ioctl = { version = "0.6", package = "ioctl-sys" }
|
||||
|
||||
[dev-dependencies]
|
||||
packet = "0.1"
|
||||
futures = "0.3"
|
||||
|
||||
[features]
|
||||
async = ["tokio", "tokio-util", "bytes", "byteorder", "futures-core"]
|
||||
|
||||
[[example]]
|
||||
name = "read-async"
|
||||
required-features = [ "async", "tokio/rt-multi-thread" ]
|
||||
|
||||
[[example]]
|
||||
name = "read-async-codec"
|
||||
required-features = [ "async", "tokio/rt-multi-thread" ]
|
||||
|
||||
[[example]]
|
||||
name = "ping-tun"
|
||||
required-features = [ "async", "tokio/rt-multi-thread" ]
|
||||
@@ -1,106 +0,0 @@
|
||||
TUN interfaces [](https://crates.io/crates/tun)  
|
||||
==============
|
||||
This crate allows the creation and usage of TUN interfaces, the aim is to make this cross-platform.
|
||||
|
||||
Usage
|
||||
-----
|
||||
First, add the following to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tun = "0.5"
|
||||
```
|
||||
|
||||
Next, add this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate tun;
|
||||
```
|
||||
|
||||
If you want to use the TUN interface with mio/tokio, you need to enable the `async` feature:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tun = { version = "0.5", features = ["async"] }
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
The following example creates and configures a TUN interface and starts reading
|
||||
packets from it.
|
||||
|
||||
```rust
|
||||
use std::io::Read;
|
||||
|
||||
extern crate tun;
|
||||
|
||||
fn main() {
|
||||
let mut config = tun::Configuration::default();
|
||||
config.address((10, 0, 0, 1))
|
||||
.netmask((255, 255, 255, 0))
|
||||
.up();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
config.platform(|config| {
|
||||
config.packet_information(true);
|
||||
});
|
||||
|
||||
let mut dev = tun::create(&config).unwrap();
|
||||
let mut buf = [0; 4096];
|
||||
|
||||
loop {
|
||||
let amount = dev.read(&mut buf).unwrap();
|
||||
println!("{:?}", &buf[0 .. amount]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Platforms
|
||||
=========
|
||||
Not every platform is supported.
|
||||
|
||||
Linux
|
||||
-----
|
||||
You will need the `tun` module to be loaded and root is required to create
|
||||
interfaces.
|
||||
|
||||
macOS
|
||||
-----
|
||||
It just werks, but you have to set up routing manually.
|
||||
|
||||
iOS
|
||||
----
|
||||
You can pass the file descriptor of the TUN device to `rust-tun` to create the interface.
|
||||
|
||||
Here is an example to create the TUN device on iOS and pass the `fd` to `rust-tun`:
|
||||
```swift
|
||||
// Swift
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
let tunnelNetworkSettings = createTunnelSettings() // Configure TUN address, DNS, mtu, routing...
|
||||
setTunnelNetworkSettings(tunnelNetworkSettings) { [weak self] error in
|
||||
let tunFd = self?.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
start_tun(tunFd)
|
||||
}
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern "C" fn start_tun(fd: std::os::raw::c_int) {
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let mut cfg = tun::Configuration::default();
|
||||
cfg.raw_fd(fd);
|
||||
let mut tun = tun::create_as_async(&cfg).unwrap();
|
||||
let mut framed = tun.into_framed();
|
||||
while let Some(packet) = framed.next().await {
|
||||
...
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use packet::{builder::Builder, icmp, ip, Packet};
|
||||
use tun::{self, Configuration, TunPacket};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut config = Configuration::default();
|
||||
|
||||
config
|
||||
.address((10, 0, 0, 1))
|
||||
.netmask((255, 255, 255, 0))
|
||||
.up();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
config.platform(|config| {
|
||||
config.packet_information(true);
|
||||
});
|
||||
|
||||
let dev = tun::create_as_async(&config).unwrap();
|
||||
|
||||
let mut framed = dev.into_framed();
|
||||
|
||||
while let Some(packet) = framed.next().await {
|
||||
match packet {
|
||||
Ok(pkt) => match ip::Packet::new(pkt.get_bytes()) {
|
||||
Ok(ip::Packet::V4(pkt)) => match icmp::Packet::new(pkt.payload()) {
|
||||
Ok(icmp) => match icmp.echo() {
|
||||
Ok(icmp) => {
|
||||
let reply = ip::v4::Builder::default()
|
||||
.id(0x42)
|
||||
.unwrap()
|
||||
.ttl(64)
|
||||
.unwrap()
|
||||
.source(pkt.destination())
|
||||
.unwrap()
|
||||
.destination(pkt.source())
|
||||
.unwrap()
|
||||
.icmp()
|
||||
.unwrap()
|
||||
.echo()
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap()
|
||||
.identifier(icmp.identifier())
|
||||
.unwrap()
|
||||
.sequence(icmp.sequence())
|
||||
.unwrap()
|
||||
.payload(icmp.payload())
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
framed.send(TunPacket::new(reply)).await.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => println!("Received an invalid packet: {:?}", err),
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => panic!("Error: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::StreamExt;
|
||||
use packet::{ip::Packet, Error};
|
||||
use tokio_util::codec::{Decoder, FramedRead};
|
||||
|
||||
pub struct IPPacketCodec;
|
||||
|
||||
impl Decoder for IPPacketCodec {
|
||||
type Item = Packet<BytesMut>;
|
||||
type Error = Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if buf.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let buf = buf.split_to(buf.len());
|
||||
Ok(match Packet::no_payload(buf) {
|
||||
Ok(pkt) => Some(pkt),
|
||||
Err(err) => {
|
||||
println!("error {:?}", err);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut config = tun::Configuration::default();
|
||||
|
||||
config
|
||||
.address((10, 0, 0, 1))
|
||||
.netmask((255, 255, 255, 0))
|
||||
.up();
|
||||
|
||||
let dev = tun::create_as_async(&config).unwrap();
|
||||
|
||||
let mut stream = FramedRead::new(dev, IPPacketCodec);
|
||||
|
||||
while let Some(packet) = stream.next().await {
|
||||
match packet {
|
||||
Ok(pkt) => println!("pkt: {:#?}", pkt),
|
||||
Err(err) => panic!("Error: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use futures::StreamExt;
|
||||
use packet::ip::Packet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut config = tun::Configuration::default();
|
||||
|
||||
config
|
||||
.address((10, 0, 0, 1))
|
||||
.netmask((255, 255, 255, 0))
|
||||
.up();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
config.platform(|config| {
|
||||
config.packet_information(true);
|
||||
});
|
||||
|
||||
let dev = tun::create_as_async(&config).unwrap();
|
||||
|
||||
let mut stream = dev.into_framed();
|
||||
|
||||
while let Some(packet) = stream.next().await {
|
||||
match packet {
|
||||
Ok(pkt) => println!("pkt: {:#?}", Packet::unchecked(pkt.get_bytes())),
|
||||
Err(err) => panic!("Error: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
let mut config = tun::Configuration::default();
|
||||
|
||||
config
|
||||
.address((10, 0, 0, 1))
|
||||
.netmask((255, 255, 255, 0))
|
||||
.up();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
config.platform(|config| {
|
||||
config.packet_information(true);
|
||||
});
|
||||
|
||||
let mut dev = tun::create(&config).unwrap();
|
||||
let mut buf = [0; 4096];
|
||||
|
||||
loop {
|
||||
let amount = dev.read(&mut buf).unwrap();
|
||||
println!("{:?}", &buf[0..amount]);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::net::{SocketAddr, SocketAddrV4};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
/// Helper trait to convert things into IPv4 addresses.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub trait IntoAddress {
|
||||
/// Convert the type to an `Ipv4Addr`.
|
||||
fn into_address(&self) -> Result<Ipv4Addr>;
|
||||
}
|
||||
|
||||
impl IntoAddress for u32 {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
Ok(Ipv4Addr::new(
|
||||
((*self) & 0xff) as u8,
|
||||
((*self >> 8) & 0xff) as u8,
|
||||
((*self >> 16) & 0xff) as u8,
|
||||
((*self >> 24) & 0xff) as u8,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for i32 {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(*self as u32).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for (u8, u8, u8, u8) {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
Ok(Ipv4Addr::new(self.0, self.1, self.2, self.3))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for str {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
self.parse().map_err(|_| Error::InvalidAddress)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a str {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(*self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for String {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a String {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for Ipv4Addr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
Ok(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a Ipv4Addr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for IpAddr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
match *self {
|
||||
IpAddr::V4(ref value) => Ok(*value),
|
||||
|
||||
IpAddr::V6(_) => Err(Error::InvalidAddress),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a IpAddr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for SocketAddrV4 {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
Ok(*self.ip())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a SocketAddrV4 {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAddress for SocketAddr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
match *self {
|
||||
SocketAddr::V4(ref value) => Ok(*value.ip()),
|
||||
|
||||
SocketAddr::V6(_) => Err(Error::InvalidAddress),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAddress for &'a SocketAddr {
|
||||
fn into_address(&self) -> Result<Ipv4Addr> {
|
||||
(&**self).into_address()
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io;
|
||||
|
||||
use byteorder::{NativeEndian, NetworkEndian, WriteBytesExt};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
/// A packet protocol IP version
|
||||
#[derive(Debug)]
|
||||
enum PacketProtocol {
|
||||
IPv4,
|
||||
IPv6,
|
||||
Other(u8),
|
||||
}
|
||||
|
||||
// Note: the protocol in the packet information header is platform dependent.
|
||||
impl PacketProtocol {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn into_pi_field(&self) -> Result<u16, io::Error> {
|
||||
match self {
|
||||
PacketProtocol::IPv4 => Ok(libc::ETH_P_IP as u16),
|
||||
PacketProtocol::IPv6 => Ok(libc::ETH_P_IPV6 as u16),
|
||||
PacketProtocol::Other(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"neither an IPv4 or IPv6 packet",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
fn into_pi_field(&self) -> Result<u16, io::Error> {
|
||||
match self {
|
||||
PacketProtocol::IPv4 => Ok(libc::PF_INET as u16),
|
||||
PacketProtocol::IPv6 => Ok(libc::PF_INET6 as u16),
|
||||
PacketProtocol::Other(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"neither an IPv4 or IPv6 packet",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Tun Packet to be sent or received on the TUN interface.
|
||||
#[derive(Debug)]
|
||||
pub struct TunPacket(PacketProtocol, Bytes);
|
||||
|
||||
/// Infer the protocol based on the first nibble in the packet buffer.
|
||||
fn infer_proto(buf: &[u8]) -> PacketProtocol {
|
||||
match buf[0] >> 4 {
|
||||
4 => PacketProtocol::IPv4,
|
||||
6 => PacketProtocol::IPv6,
|
||||
p => PacketProtocol::Other(p),
|
||||
}
|
||||
}
|
||||
|
||||
impl TunPacket {
|
||||
/// Create a new `TunPacket` based on a byte slice.
|
||||
pub fn new(bytes: Vec<u8>) -> TunPacket {
|
||||
let proto = infer_proto(&bytes);
|
||||
TunPacket(proto, Bytes::from(bytes))
|
||||
}
|
||||
|
||||
/// Return this packet's bytes.
|
||||
pub fn get_bytes(&self) -> &[u8] {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Bytes {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
/// A TunPacket Encoder/Decoder.
|
||||
pub struct TunPacketCodec(bool, i32);
|
||||
|
||||
impl TunPacketCodec {
|
||||
/// Create a new `TunPacketCodec` specifying whether the underlying
|
||||
/// tunnel Device has enabled the packet information header.
|
||||
pub fn new(pi: bool, mtu: i32) -> TunPacketCodec {
|
||||
TunPacketCodec(pi, mtu)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for TunPacketCodec {
|
||||
type Item = TunPacket;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if buf.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut pkt = buf.split_to(buf.len());
|
||||
|
||||
// reserve enough space for the next packet
|
||||
if self.0 {
|
||||
buf.reserve(self.1 as usize + 4);
|
||||
} else {
|
||||
buf.reserve(self.1 as usize);
|
||||
}
|
||||
|
||||
// if the packet information is enabled we have to ignore the first 4 bytes
|
||||
if self.0 {
|
||||
let _ = pkt.split_to(4);
|
||||
}
|
||||
|
||||
let proto = infer_proto(pkt.as_ref());
|
||||
Ok(Some(TunPacket(proto, pkt.freeze())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<TunPacket> for TunPacketCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: TunPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
dst.reserve(item.get_bytes().len() + 4);
|
||||
match item {
|
||||
TunPacket(proto, bytes) if self.0 => {
|
||||
// build the packet information header comprising of 2 u16
|
||||
// fields: flags and protocol.
|
||||
let mut buf = Vec::<u8>::with_capacity(4);
|
||||
|
||||
// flags is always 0
|
||||
buf.write_u16::<NativeEndian>(0).unwrap();
|
||||
// write the protocol as network byte order
|
||||
buf.write_u16::<NetworkEndian>(proto.into_pi_field()?)
|
||||
.unwrap();
|
||||
|
||||
dst.put_slice(&buf);
|
||||
dst.put(bytes);
|
||||
}
|
||||
TunPacket(_, bytes) => dst.put(bytes),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io;
|
||||
use std::io::{IoSlice, Read, Write};
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::ready;
|
||||
use tokio::io::unix::AsyncFd;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio_util::codec::Framed;
|
||||
|
||||
use crate::device::Device as D;
|
||||
use crate::platform::{Device, Queue};
|
||||
use crate::r#async::codec::*;
|
||||
|
||||
/// An async TUN device wrapper around a TUN device.
|
||||
pub struct AsyncDevice {
|
||||
inner: AsyncFd<Device>,
|
||||
}
|
||||
|
||||
impl AsyncDevice {
|
||||
/// Create a new `AsyncDevice` wrapping around a `Device`.
|
||||
pub fn new(device: Device) -> io::Result<AsyncDevice> {
|
||||
device.set_nonblock()?;
|
||||
Ok(AsyncDevice {
|
||||
inner: AsyncFd::new(device)?,
|
||||
})
|
||||
}
|
||||
/// Returns a shared reference to the underlying Device object
|
||||
pub fn get_ref(&self) -> &Device {
|
||||
self.inner.get_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying Device object
|
||||
pub fn get_mut(&mut self) -> &mut Device {
|
||||
self.inner.get_mut()
|
||||
}
|
||||
|
||||
/// Consumes this AsyncDevice and return a Framed object (unified Stream and Sink interface)
|
||||
pub fn into_framed(mut self) -> Framed<Self, TunPacketCodec> {
|
||||
let pi = self.get_mut().has_packet_information();
|
||||
let codec = TunPacketCodec::new(pi, self.inner.get_ref().mtu().unwrap_or(1504));
|
||||
Framed::new(self, codec)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AsyncDevice {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
|
||||
let rbuf = buf.initialize_unfilled();
|
||||
match guard.try_io(|inner| inner.get_mut().read(rbuf)) {
|
||||
Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for AsyncDevice {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
|
||||
match guard.try_io(|inner| inner.get_mut().write(buf)) {
|
||||
Ok(res) => return Poll::Ready(res),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
|
||||
match guard.try_io(|inner| inner.get_mut().flush()) {
|
||||
Ok(res) => return Poll::Ready(res),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
|
||||
match guard.try_io(|inner| inner.get_mut().write_vectored(bufs)) {
|
||||
Ok(res) => return Poll::Ready(res),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// An async TUN device queue wrapper around a TUN device queue.
|
||||
pub struct AsyncQueue {
|
||||
inner: AsyncFd<Queue>,
|
||||
}
|
||||
|
||||
impl AsyncQueue {
|
||||
/// Create a new `AsyncQueue` wrapping around a `Queue`.
|
||||
pub fn new(queue: Queue) -> io::Result<AsyncQueue> {
|
||||
queue.set_nonblock()?;
|
||||
Ok(AsyncQueue {
|
||||
inner: AsyncFd::new(queue)?,
|
||||
})
|
||||
}
|
||||
/// Returns a shared reference to the underlying Queue object
|
||||
pub fn get_ref(&self) -> &Queue {
|
||||
self.inner.get_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying Queue object
|
||||
pub fn get_mut(&mut self) -> &mut Queue {
|
||||
self.inner.get_mut()
|
||||
}
|
||||
|
||||
/// Consumes this AsyncQueue and return a Framed object (unified Stream and Sink interface)
|
||||
pub fn into_framed(mut self) -> Framed<Self, TunPacketCodec> {
|
||||
let pi = self.get_mut().has_packet_information();
|
||||
let codec = TunPacketCodec::new(pi, 1504);
|
||||
Framed::new(self, codec)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AsyncQueue {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
|
||||
let rbuf = buf.initialize_unfilled();
|
||||
match guard.try_io(|inner| inner.get_mut().read(rbuf)) {
|
||||
Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for AsyncQueue {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
|
||||
match guard.try_io(|inner| inner.get_mut().write(buf)) {
|
||||
Ok(res) => return Poll::Ready(res),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?;
|
||||
match guard.try_io(|inner| inner.get_mut().flush()) {
|
||||
Ok(res) => return Poll::Ready(res),
|
||||
Err(_wb) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Async specific modules.
|
||||
|
||||
use crate::error;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::platform::create;
|
||||
|
||||
mod device;
|
||||
pub use self::device::{AsyncDevice, AsyncQueue};
|
||||
|
||||
mod codec;
|
||||
pub use self::codec::{TunPacket, TunPacketCodec};
|
||||
|
||||
/// Create a TUN device with the given name.
|
||||
pub fn create_as_async(configuration: &Configuration) -> Result<AsyncDevice, error::Error> {
|
||||
let device = create(&configuration)?;
|
||||
AsyncDevice::new(device).map_err(|err| err.into())
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use crate::address::IntoAddress;
|
||||
use crate::platform;
|
||||
|
||||
/// TUN interface OSI layer of operation.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Layer {
|
||||
L2,
|
||||
L3,
|
||||
}
|
||||
|
||||
impl Default for Layer {
|
||||
fn default() -> Self {
|
||||
Layer::L3
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration builder for a TUN interface.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Configuration {
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) platform: platform::Configuration,
|
||||
|
||||
pub(crate) address: Option<Ipv4Addr>,
|
||||
pub(crate) destination: Option<Ipv4Addr>,
|
||||
pub(crate) broadcast: Option<Ipv4Addr>,
|
||||
pub(crate) netmask: Option<Ipv4Addr>,
|
||||
pub(crate) mtu: Option<i32>,
|
||||
pub(crate) enabled: Option<bool>,
|
||||
pub(crate) layer: Option<Layer>,
|
||||
pub(crate) queues: Option<usize>,
|
||||
pub(crate) raw_fd: Option<RawFd>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
/// Access the platform dependant configuration.
|
||||
pub fn platform<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut platform::Configuration),
|
||||
{
|
||||
f(&mut self.platform);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name.
|
||||
pub fn name<S: AsRef<str>>(&mut self, name: S) -> &mut Self {
|
||||
self.name = Some(name.as_ref().into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the address.
|
||||
pub fn address<A: IntoAddress>(&mut self, value: A) -> &mut Self {
|
||||
self.address = Some(value.into_address().unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the destination address.
|
||||
pub fn destination<A: IntoAddress>(&mut self, value: A) -> &mut Self {
|
||||
self.destination = Some(value.into_address().unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the broadcast address.
|
||||
pub fn broadcast<A: IntoAddress>(&mut self, value: A) -> &mut Self {
|
||||
self.broadcast = Some(value.into_address().unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the netmask.
|
||||
pub fn netmask<A: IntoAddress>(&mut self, value: A) -> &mut Self {
|
||||
self.netmask = Some(value.into_address().unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the MTU.
|
||||
pub fn mtu(&mut self, value: i32) -> &mut Self {
|
||||
self.mtu = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the interface to be enabled once created.
|
||||
pub fn up(&mut self) -> &mut Self {
|
||||
self.enabled = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the interface to be disabled once created.
|
||||
pub fn down(&mut self) -> &mut Self {
|
||||
self.enabled = Some(false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the OSI layer of operation.
|
||||
pub fn layer(&mut self, value: Layer) -> &mut Self {
|
||||
self.layer = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the number of queues.
|
||||
pub fn queues(&mut self, value: usize) -> &mut Self {
|
||||
self.queues = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the raw fd.
|
||||
pub fn raw_fd(&mut self, fd: RawFd) -> &mut Self {
|
||||
self.raw_fd = Some(fd);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::error::*;
|
||||
|
||||
/// A TUN device.
|
||||
pub trait Device: Read + Write {
|
||||
type Queue: Read + Write;
|
||||
|
||||
/// Reconfigure the device.
|
||||
fn configure(&mut self, config: &Configuration) -> Result<()> {
|
||||
if let Some(ip) = config.address {
|
||||
self.set_address(ip)?;
|
||||
}
|
||||
|
||||
if let Some(ip) = config.destination {
|
||||
self.set_destination(ip)?;
|
||||
}
|
||||
|
||||
if let Some(ip) = config.broadcast {
|
||||
self.set_broadcast(ip)?;
|
||||
}
|
||||
|
||||
if let Some(ip) = config.netmask {
|
||||
self.set_netmask(ip)?;
|
||||
}
|
||||
|
||||
if let Some(mtu) = config.mtu {
|
||||
self.set_mtu(mtu)?;
|
||||
}
|
||||
|
||||
if let Some(enabled) = config.enabled {
|
||||
self.enabled(enabled)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the device name.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Set the device name.
|
||||
fn set_name(&mut self, name: &str) -> Result<()>;
|
||||
|
||||
/// Turn on or off the interface.
|
||||
fn enabled(&mut self, value: bool) -> Result<()>;
|
||||
|
||||
/// Get the address.
|
||||
fn address(&self) -> Result<Ipv4Addr>;
|
||||
|
||||
/// Set the address.
|
||||
fn set_address(&mut self, value: Ipv4Addr) -> Result<()>;
|
||||
|
||||
/// Get the destination address.
|
||||
fn destination(&self) -> Result<Ipv4Addr>;
|
||||
|
||||
/// Set the destination address.
|
||||
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()>;
|
||||
|
||||
/// Get the broadcast address.
|
||||
fn broadcast(&self) -> Result<Ipv4Addr>;
|
||||
|
||||
/// Set the broadcast address.
|
||||
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()>;
|
||||
|
||||
/// Get the netmask.
|
||||
fn netmask(&self) -> Result<Ipv4Addr>;
|
||||
|
||||
/// Set the netmask.
|
||||
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()>;
|
||||
|
||||
/// Get the MTU.
|
||||
fn mtu(&self) -> Result<i32>;
|
||||
|
||||
/// Set the MTU.
|
||||
fn set_mtu(&mut self, value: i32) -> Result<()>;
|
||||
|
||||
/// Get a device queue.
|
||||
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue>;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::{ffi, io, num};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("invalid configuration")]
|
||||
InvalidConfig,
|
||||
|
||||
#[error("not implementated")]
|
||||
NotImplemented,
|
||||
|
||||
#[error("device name too long")]
|
||||
NameTooLong,
|
||||
|
||||
#[error("invalid device name")]
|
||||
InvalidName,
|
||||
|
||||
#[error("invalid address")]
|
||||
InvalidAddress,
|
||||
|
||||
#[error("invalid file descriptor")]
|
||||
InvalidDescriptor,
|
||||
|
||||
#[error("unsuported network layer of operation")]
|
||||
UnsupportedLayer,
|
||||
|
||||
#[error("invalid queues number")]
|
||||
InvalidQueuesNumber,
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Nul(#[from] ffi::NulError),
|
||||
|
||||
#[error(transparent)]
|
||||
ParseNum(#[from] num::ParseIntError),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -1,53 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
mod error;
|
||||
pub use crate::error::*;
|
||||
|
||||
mod address;
|
||||
pub use crate::address::IntoAddress;
|
||||
|
||||
mod device;
|
||||
pub use crate::device::Device;
|
||||
|
||||
mod configuration;
|
||||
pub use crate::configuration::{Configuration, Layer};
|
||||
|
||||
pub mod platform;
|
||||
pub use crate::platform::create;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "async",
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)
|
||||
))]
|
||||
pub mod r#async;
|
||||
#[cfg(all(
|
||||
feature = "async",
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)
|
||||
))]
|
||||
pub use r#async::*;
|
||||
|
||||
pub fn configure() -> Configuration {
|
||||
Configuration::default()
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::device::Device as D;
|
||||
use crate::error::*;
|
||||
use crate::platform::posix::{self, Fd};
|
||||
|
||||
/// A TUN device for Android.
|
||||
pub struct Device {
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Create a new `Device` for the given `Configuration`.
|
||||
pub fn new(config: &Configuration) -> Result<Self> {
|
||||
let fd = match config.raw_fd {
|
||||
Some(raw_fd) => raw_fd,
|
||||
_ => return Err(Error::InvalidConfig),
|
||||
};
|
||||
let device = {
|
||||
let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
Device {
|
||||
queue: Queue { tun: tun },
|
||||
}
|
||||
};
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
/// Split the interface into a `Reader` and `Writer`.
|
||||
pub fn split(self) -> (posix::Reader, posix::Writer) {
|
||||
let fd = Arc::new(self.queue.tun);
|
||||
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
|
||||
}
|
||||
|
||||
/// Return whether the device has packet information
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
self.queue.has_packet_information()
|
||||
}
|
||||
|
||||
/// Set non-blocking mode
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.queue.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.queue.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.queue.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.queue.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl D for Device {
|
||||
type Queue = Queue;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
return "";
|
||||
}
|
||||
|
||||
fn set_name(&mut self, value: &str) -> Result<()> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn enabled(&mut self, value: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destination(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn broadcast(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn netmask(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mtu(&self) -> Result<i32> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_mtu(&mut self, value: i32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
|
||||
if index > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(&mut self.queue)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Device {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.queue.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Device {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.queue.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Queue {
|
||||
tun: Fd,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
// on Android this is always the case
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.tun.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Queue {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.tun.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Queue {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.tun.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Queue {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Queue {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Android specific functionality.
|
||||
|
||||
mod device;
|
||||
pub use self::device::{Device, Queue};
|
||||
|
||||
use crate::configuration::Configuration as C;
|
||||
use crate::error::*;
|
||||
|
||||
/// Android-only interface configuration.
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct Configuration {}
|
||||
|
||||
/// Create a TUN device with the given name.
|
||||
pub fn create(configuration: &C) -> Result<Device> {
|
||||
Device::new(&configuration)
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::device::Device as D;
|
||||
use crate::error::*;
|
||||
use crate::platform::posix::{self, Fd};
|
||||
|
||||
/// A TUN device for iOS.
|
||||
pub struct Device {
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Create a new `Device` for the given `Configuration`.
|
||||
pub fn new(config: &Configuration) -> Result<Self> {
|
||||
let fd = match config.raw_fd {
|
||||
Some(raw_fd) => raw_fd,
|
||||
_ => return Err(Error::InvalidConfig),
|
||||
};
|
||||
let mut device = unsafe {
|
||||
let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
Device {
|
||||
queue: Queue { tun: tun },
|
||||
}
|
||||
};
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
/// Split the interface into a `Reader` and `Writer`.
|
||||
pub fn split(self) -> (posix::Reader, posix::Writer) {
|
||||
let fd = Arc::new(self.queue.tun);
|
||||
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
|
||||
}
|
||||
|
||||
/// Return whether the device has packet information
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
self.queue.has_packet_information()
|
||||
}
|
||||
|
||||
/// Set non-blocking mode
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.queue.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.queue.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.queue.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.queue.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl D for Device {
|
||||
type Queue = Queue;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
return "";
|
||||
}
|
||||
|
||||
fn set_name(&mut self, value: &str) -> Result<()> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn enabled(&mut self, value: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destination(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn broadcast(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn netmask(&self) -> Result<Ipv4Addr> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mtu(&self) -> Result<i32> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn set_mtu(&mut self, value: i32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
|
||||
if index > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(&mut self.queue)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Device {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.queue.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Device {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.queue.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Queue {
|
||||
tun: Fd,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
// on ios this is always the case
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.tun.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Queue {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.tun.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Queue {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.tun.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Queue {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Queue {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! iOS specific functionality.
|
||||
|
||||
mod device;
|
||||
pub use self::device::{Device, Queue};
|
||||
|
||||
use crate::configuration::Configuration as C;
|
||||
use crate::error::*;
|
||||
|
||||
/// iOS-only interface configuration.
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct Configuration {}
|
||||
|
||||
/// Create a TUN device with the given name.
|
||||
pub fn create(configuration: &C) -> Result<Device> {
|
||||
Device::new(&configuration)
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use libc;
|
||||
use libc::{c_char, c_short};
|
||||
use libc::{AF_INET, O_RDWR, SOCK_DGRAM};
|
||||
|
||||
use crate::configuration::{Configuration, Layer};
|
||||
use crate::device::Device as D;
|
||||
use crate::error::*;
|
||||
use crate::platform::linux::sys::*;
|
||||
use crate::platform::posix::{self, Fd, SockAddr};
|
||||
|
||||
/// A TUN device using the TUN/TAP Linux driver.
|
||||
pub struct Device {
|
||||
name: String,
|
||||
queues: Vec<Queue>,
|
||||
ctl: Fd,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Create a new `Device` for the given `Configuration`.
|
||||
pub fn new(config: &Configuration) -> Result<Self> {
|
||||
let mut device = unsafe {
|
||||
let dev = match config.name.as_ref() {
|
||||
Some(name) => {
|
||||
let name = CString::new(name.clone())?;
|
||||
|
||||
if name.as_bytes_with_nul().len() > IFNAMSIZ {
|
||||
return Err(Error::NameTooLong);
|
||||
}
|
||||
|
||||
Some(name)
|
||||
}
|
||||
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut queues = Vec::new();
|
||||
|
||||
let mut req: ifreq = mem::zeroed();
|
||||
|
||||
if let Some(dev) = dev.as_ref() {
|
||||
ptr::copy_nonoverlapping(
|
||||
dev.as_ptr() as *const c_char,
|
||||
req.ifrn.name.as_mut_ptr(),
|
||||
dev.as_bytes().len(),
|
||||
);
|
||||
}
|
||||
|
||||
let device_type: c_short = config.layer.unwrap_or(Layer::L3).into();
|
||||
|
||||
let queues_num = config.queues.unwrap_or(1);
|
||||
if queues_num < 1 {
|
||||
return Err(Error::InvalidQueuesNumber);
|
||||
}
|
||||
|
||||
req.ifru.flags = device_type
|
||||
| if config.platform.packet_information {
|
||||
0
|
||||
} else {
|
||||
IFF_NO_PI
|
||||
}
|
||||
| if queues_num > 1 { IFF_MULTI_QUEUE } else { 0 };
|
||||
|
||||
for _ in 0..queues_num {
|
||||
let tun = Fd::new(libc::open(b"/dev/net/tun\0".as_ptr() as *const _, O_RDWR))
|
||||
.map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
if tunsetiff(tun.0, &mut req as *mut _ as *mut _) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
queues.push(Queue {
|
||||
tun,
|
||||
pi_enabled: config.platform.packet_information,
|
||||
});
|
||||
}
|
||||
|
||||
let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0))
|
||||
.map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
Device {
|
||||
name: CStr::from_ptr(req.ifrn.name.as_ptr())
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
queues,
|
||||
ctl,
|
||||
}
|
||||
};
|
||||
|
||||
device.configure(config)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
/// Prepare a new request.
|
||||
unsafe fn request(&self) -> ifreq {
|
||||
let mut req: ifreq = mem::zeroed();
|
||||
ptr::copy_nonoverlapping(
|
||||
self.name.as_ptr() as *const c_char,
|
||||
req.ifrn.name.as_mut_ptr(),
|
||||
self.name.len(),
|
||||
);
|
||||
|
||||
req
|
||||
}
|
||||
|
||||
/// Make the device persistent.
|
||||
pub fn persist(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
if tunsetpersist(self.as_raw_fd(), &1) < 0 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the owner of the device.
|
||||
pub fn user(&mut self, value: i32) -> Result<()> {
|
||||
unsafe {
|
||||
if tunsetowner(self.as_raw_fd(), &value) < 0 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the group of the device.
|
||||
pub fn group(&mut self, value: i32) -> Result<()> {
|
||||
unsafe {
|
||||
if tunsetgroup(self.as_raw_fd(), &value) < 0 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn split(mut self) -> (posix::Reader, posix::Writer) {
|
||||
let queue = self.queues.swap_remove(0);
|
||||
let fd = Arc::new(queue.tun);
|
||||
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
|
||||
}
|
||||
/// Return whether the device has packet information
|
||||
pub fn has_packet_information(&mut self) -> bool {
|
||||
self.queues[0].has_packet_information()
|
||||
}
|
||||
|
||||
/// Set non-blocking mode
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.queues[0].set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.queues[0].read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.queues[0].read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.queues[0].write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.queues[0].flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.queues[0].write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl D for Device {
|
||||
type Queue = Queue;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn set_name(&mut self, value: &str) -> Result<()> {
|
||||
unsafe {
|
||||
let name = CString::new(value)?;
|
||||
|
||||
if name.as_bytes_with_nul().len() > IFNAMSIZ {
|
||||
return Err(Error::NameTooLong);
|
||||
}
|
||||
|
||||
let mut req = self.request();
|
||||
ptr::copy_nonoverlapping(
|
||||
name.as_ptr() as *const c_char,
|
||||
req.ifru.newname.as_mut_ptr(),
|
||||
value.len(),
|
||||
);
|
||||
|
||||
if siocsifname(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
self.name = value.into();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn enabled(&mut self, value: bool) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
if value {
|
||||
req.ifru.flags |= IFF_UP | IFF_RUNNING;
|
||||
} else {
|
||||
req.ifru.flags &= !IFF_UP;
|
||||
}
|
||||
|
||||
if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.addr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.addr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn destination(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.dstaddr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.dstaddr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.broadaddr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.broadaddr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn netmask(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.netmask).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.netmask = SockAddr::from(value).into();
|
||||
|
||||
if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn mtu(&self) -> Result<i32> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(req.ifru.mtu)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_mtu(&mut self, value: i32) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.mtu = value;
|
||||
|
||||
if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
|
||||
self.queues.get_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Device {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.queues[0].as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Device {
|
||||
fn into_raw_fd(mut self) -> RawFd {
|
||||
// It is Ok to swap the first queue with the last one, because the self will be dropped afterwards
|
||||
let queue = self.queues.swap_remove(0);
|
||||
queue.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Queue {
|
||||
tun: Fd,
|
||||
pi_enabled: bool,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn has_packet_information(&mut self) -> bool {
|
||||
self.pi_enabled
|
||||
}
|
||||
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.tun.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Queue {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Queue {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Queue {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.tun.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Queue {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.tun.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Layer> for c_short {
|
||||
fn from(layer: Layer) -> Self {
|
||||
match layer {
|
||||
Layer::L2 => IFF_TAP,
|
||||
Layer::L3 => IFF_TUN,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Linux specific functionality.
|
||||
|
||||
pub mod sys;
|
||||
|
||||
mod device;
|
||||
pub use self::device::{Device, Queue};
|
||||
|
||||
use crate::configuration::Configuration as C;
|
||||
use crate::error::*;
|
||||
|
||||
/// Linux-only interface configuration.
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct Configuration {
|
||||
pub(crate) packet_information: bool,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
/// Enable or disable packet information, when enabled the first 4 bytes of
|
||||
/// each packet is a header with flags and protocol type.
|
||||
pub fn packet_information(&mut self, value: bool) -> &mut Self {
|
||||
self.packet_information = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a TUN device with the given name.
|
||||
pub fn create(configuration: &C) -> Result<Device> {
|
||||
Device::new(configuration)
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Bindings to internal Linux stuff.
|
||||
|
||||
use ioctl::*;
|
||||
use libc::sockaddr;
|
||||
use libc::{c_char, c_int, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void};
|
||||
|
||||
pub const IFNAMSIZ: usize = 16;
|
||||
|
||||
pub const IFF_UP: c_short = 0x1;
|
||||
pub const IFF_RUNNING: c_short = 0x40;
|
||||
|
||||
pub const IFF_TUN: c_short = 0x0001;
|
||||
pub const IFF_TAP: c_short = 0x0002;
|
||||
pub const IFF_NO_PI: c_short = 0x1000;
|
||||
pub const IFF_MULTI_QUEUE: c_short = 0x0100;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifmap {
|
||||
pub mem_start: c_ulong,
|
||||
pub mem_end: c_ulong,
|
||||
pub base_addr: c_ushort,
|
||||
pub irq: c_uchar,
|
||||
pub dma: c_uchar,
|
||||
pub port: c_uchar,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifsu {
|
||||
pub raw_hdlc_proto: *mut c_void,
|
||||
pub cisco: *mut c_void,
|
||||
pub fr: *mut c_void,
|
||||
pub fr_pvc: *mut c_void,
|
||||
pub fr_pvc_info: *mut c_void,
|
||||
pub sync: *mut c_void,
|
||||
pub te1: *mut c_void,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct if_settings {
|
||||
pub type_: c_uint,
|
||||
pub size: c_uint,
|
||||
pub ifsu: ifsu,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifrn {
|
||||
pub name: [c_char; IFNAMSIZ],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifru {
|
||||
pub addr: sockaddr,
|
||||
pub dstaddr: sockaddr,
|
||||
pub broadaddr: sockaddr,
|
||||
pub netmask: sockaddr,
|
||||
pub hwaddr: sockaddr,
|
||||
|
||||
pub flags: c_short,
|
||||
pub ivalue: c_int,
|
||||
pub mtu: c_int,
|
||||
pub map: ifmap,
|
||||
pub slave: [c_char; IFNAMSIZ],
|
||||
pub newname: [c_char; IFNAMSIZ],
|
||||
pub data: *mut c_void,
|
||||
pub settings: if_settings,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifreq {
|
||||
pub ifrn: ifrn,
|
||||
pub ifru: ifru,
|
||||
}
|
||||
|
||||
ioctl!(bad read siocgifflags with 0x8913; ifreq);
|
||||
ioctl!(bad write siocsifflags with 0x8914; ifreq);
|
||||
ioctl!(bad read siocgifaddr with 0x8915; ifreq);
|
||||
ioctl!(bad write siocsifaddr with 0x8916; ifreq);
|
||||
ioctl!(bad read siocgifdstaddr with 0x8917; ifreq);
|
||||
ioctl!(bad write siocsifdstaddr with 0x8918; ifreq);
|
||||
ioctl!(bad read siocgifbrdaddr with 0x8919; ifreq);
|
||||
ioctl!(bad write siocsifbrdaddr with 0x891a; ifreq);
|
||||
ioctl!(bad read siocgifnetmask with 0x891b; ifreq);
|
||||
ioctl!(bad write siocsifnetmask with 0x891c; ifreq);
|
||||
ioctl!(bad read siocgifmtu with 0x8921; ifreq);
|
||||
ioctl!(bad write siocsifmtu with 0x8922; ifreq);
|
||||
ioctl!(bad write siocsifname with 0x8923; ifreq);
|
||||
|
||||
ioctl!(write tunsetiff with b'T', 202; c_int);
|
||||
ioctl!(write tunsetpersist with b'T', 203; c_int);
|
||||
ioctl!(write tunsetowner with b'T', 204; c_int);
|
||||
ioctl!(write tunsetgroup with b'T', 206; c_int);
|
||||
@@ -1,438 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use libc;
|
||||
use libc::{c_char, c_uint, c_void, sockaddr, socklen_t, AF_INET, SOCK_DGRAM};
|
||||
|
||||
use crate::configuration::{Configuration, Layer};
|
||||
use crate::device::Device as D;
|
||||
use crate::error::*;
|
||||
use crate::platform::macos::sys::*;
|
||||
use crate::platform::posix::{self, Fd, SockAddr};
|
||||
|
||||
/// A TUN device using the TUN macOS driver.
|
||||
pub struct Device {
|
||||
name: String,
|
||||
queue: Queue,
|
||||
ctl: Fd,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Create a new `Device` for the given `Configuration`.
|
||||
pub fn new(config: &Configuration) -> Result<Self> {
|
||||
let id = if let Some(name) = config.name.as_ref() {
|
||||
if name.len() > IFNAMSIZ {
|
||||
return Err(Error::NameTooLong);
|
||||
}
|
||||
|
||||
if !name.starts_with("utun") {
|
||||
return Err(Error::InvalidName);
|
||||
}
|
||||
|
||||
name[4..].parse()?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if config.layer.filter(|l| *l != Layer::L3).is_some() {
|
||||
return Err(Error::UnsupportedLayer);
|
||||
}
|
||||
|
||||
let queues_number = config.queues.unwrap_or(1);
|
||||
if queues_number != 1 {
|
||||
return Err(Error::InvalidQueuesNumber);
|
||||
}
|
||||
|
||||
let mut device = unsafe {
|
||||
let tun = Fd::new(libc::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL))
|
||||
.map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
let mut info = ctl_info {
|
||||
ctl_id: 0,
|
||||
ctl_name: {
|
||||
let mut buffer = [0; 96];
|
||||
for (i, o) in UTUN_CONTROL_NAME.as_bytes().iter().zip(buffer.iter_mut()) {
|
||||
*o = *i as _;
|
||||
}
|
||||
buffer
|
||||
},
|
||||
};
|
||||
|
||||
if ctliocginfo(tun.0, &mut info as *mut _ as *mut _) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
let addr = sockaddr_ctl {
|
||||
sc_id: info.ctl_id,
|
||||
sc_len: mem::size_of::<sockaddr_ctl>() as _,
|
||||
sc_family: AF_SYSTEM,
|
||||
ss_sysaddr: AF_SYS_CONTROL,
|
||||
sc_unit: id as c_uint,
|
||||
sc_reserved: [0; 5],
|
||||
};
|
||||
|
||||
if libc::connect(
|
||||
tun.0,
|
||||
&addr as *const sockaddr_ctl as *const sockaddr,
|
||||
mem::size_of_val(&addr) as socklen_t,
|
||||
) < 0
|
||||
{
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
let mut name = [0u8; 64];
|
||||
let mut name_len: socklen_t = 64;
|
||||
|
||||
if libc::getsockopt(
|
||||
tun.0,
|
||||
SYSPROTO_CONTROL,
|
||||
UTUN_OPT_IFNAME,
|
||||
&mut name as *mut _ as *mut c_void,
|
||||
&mut name_len as *mut socklen_t,
|
||||
) < 0
|
||||
{
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0))
|
||||
.map_err(|_| io::Error::last_os_error())?;
|
||||
|
||||
Device {
|
||||
name: CStr::from_ptr(name.as_ptr() as *const c_char)
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
queue: Queue { tun: tun },
|
||||
ctl: ctl,
|
||||
}
|
||||
};
|
||||
|
||||
device.configure(&config)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
/// Prepare a new request.
|
||||
pub unsafe fn request(&self) -> ifreq {
|
||||
let mut req: ifreq = mem::zeroed();
|
||||
ptr::copy_nonoverlapping(
|
||||
self.name.as_ptr() as *const c_char,
|
||||
req.ifrn.name.as_mut_ptr(),
|
||||
self.name.len(),
|
||||
);
|
||||
|
||||
req
|
||||
}
|
||||
|
||||
/// Set the IPv4 alias of the device.
|
||||
pub fn set_alias(&mut self, addr: Ipv4Addr, broadaddr: Ipv4Addr, mask: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req: ifaliasreq = mem::zeroed();
|
||||
ptr::copy_nonoverlapping(
|
||||
self.name.as_ptr() as *const c_char,
|
||||
req.ifran.as_mut_ptr(),
|
||||
self.name.len(),
|
||||
);
|
||||
|
||||
req.addr = SockAddr::from(addr).into();
|
||||
req.broadaddr = SockAddr::from(broadaddr).into();
|
||||
req.mask = SockAddr::from(mask).into();
|
||||
|
||||
if siocaifaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Split the interface into a `Reader` and `Writer`.
|
||||
pub fn split(self) -> (posix::Reader, posix::Writer) {
|
||||
let fd = Arc::new(self.queue.tun);
|
||||
(posix::Reader(fd.clone()), posix::Writer(fd.clone()))
|
||||
}
|
||||
|
||||
/// Return whether the device has packet information
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
self.queue.has_packet_information()
|
||||
}
|
||||
|
||||
/// Set non-blocking mode
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.queue.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.queue.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.queue.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.queue.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.queue.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl D for Device {
|
||||
type Queue = Queue;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// XXX: Cannot set interface name on Darwin.
|
||||
fn set_name(&mut self, value: &str) -> Result<()> {
|
||||
Err(Error::InvalidName)
|
||||
}
|
||||
|
||||
fn enabled(&mut self, value: bool) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
if value {
|
||||
req.ifru.flags |= IFF_UP | IFF_RUNNING;
|
||||
} else {
|
||||
req.ifru.flags &= !IFF_UP;
|
||||
}
|
||||
|
||||
if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.addr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_address(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.addr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn destination(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.dstaddr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.dstaddr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::new(&req.ifru.broadaddr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.broadaddr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn netmask(&self) -> Result<Ipv4Addr> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
SockAddr::unchecked(&req.ifru.addr).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.addr = SockAddr::from(value).into();
|
||||
|
||||
if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn mtu(&self) -> Result<i32> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
|
||||
if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(req.ifru.mtu)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_mtu(&mut self, value: i32) -> Result<()> {
|
||||
unsafe {
|
||||
let mut req = self.request();
|
||||
req.ifru.mtu = value;
|
||||
|
||||
if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 {
|
||||
return Err(io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> {
|
||||
if index > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(&mut self.queue)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Device {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.queue.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Device {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.queue.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Queue {
|
||||
tun: Fd,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn has_packet_information(&self) -> bool {
|
||||
// on macos this is always the case
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
self.tun.set_nonblock()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Queue {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.tun.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Queue {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.tun.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Queue {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.tun.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.tun.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Queue {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.tun.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.tun.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.tun.write_vectored(bufs)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! macOS specific functionality.
|
||||
|
||||
pub mod sys;
|
||||
|
||||
mod device;
|
||||
pub use self::device::{Device, Queue};
|
||||
|
||||
use crate::configuration::Configuration as C;
|
||||
use crate::error::*;
|
||||
|
||||
/// macOS-only interface configuration.
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct Configuration {}
|
||||
|
||||
/// Create a TUN device with the given name.
|
||||
pub fn create(configuration: &C) -> Result<Device> {
|
||||
Device::new(&configuration)
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Bindings to internal macOS stuff.
|
||||
|
||||
use ioctl::*;
|
||||
use libc::sockaddr;
|
||||
use libc::{c_char, c_int, c_short, c_uint, c_ushort, c_void};
|
||||
|
||||
pub const IFNAMSIZ: usize = 16;
|
||||
|
||||
pub const IFF_UP: c_short = 0x1;
|
||||
pub const IFF_RUNNING: c_short = 0x40;
|
||||
|
||||
pub const AF_SYS_CONTROL: c_ushort = 2;
|
||||
pub const AF_SYSTEM: c_char = 32;
|
||||
pub const PF_SYSTEM: c_int = AF_SYSTEM as c_int;
|
||||
pub const SYSPROTO_CONTROL: c_int = 2;
|
||||
pub const UTUN_OPT_IFNAME: c_int = 2;
|
||||
pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ctl_info {
|
||||
pub ctl_id: c_uint,
|
||||
pub ctl_name: [c_char; 96],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct sockaddr_ctl {
|
||||
pub sc_len: c_char,
|
||||
pub sc_family: c_char,
|
||||
pub ss_sysaddr: c_ushort,
|
||||
pub sc_id: c_uint,
|
||||
pub sc_unit: c_uint,
|
||||
pub sc_reserved: [c_uint; 5],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifrn {
|
||||
pub name: [c_char; IFNAMSIZ],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifdevmtu {
|
||||
pub current: c_int,
|
||||
pub min: c_int,
|
||||
pub max: c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifku {
|
||||
pub ptr: *mut c_void,
|
||||
pub value: c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifkpi {
|
||||
pub module_id: c_uint,
|
||||
pub type_: c_uint,
|
||||
pub ifku: ifku,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ifru {
|
||||
pub addr: sockaddr,
|
||||
pub dstaddr: sockaddr,
|
||||
pub broadaddr: sockaddr,
|
||||
|
||||
pub flags: c_short,
|
||||
pub metric: c_int,
|
||||
pub mtu: c_int,
|
||||
pub phys: c_int,
|
||||
pub media: c_int,
|
||||
pub intval: c_int,
|
||||
pub data: *mut c_void,
|
||||
pub devmtu: ifdevmtu,
|
||||
pub wake_flags: c_uint,
|
||||
pub route_refcnt: c_uint,
|
||||
pub cap: [c_int; 2],
|
||||
pub functional_type: c_uint,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifreq {
|
||||
pub ifrn: ifrn,
|
||||
pub ifru: ifru,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ifaliasreq {
|
||||
pub ifran: [c_char; IFNAMSIZ],
|
||||
pub addr: sockaddr,
|
||||
pub broadaddr: sockaddr,
|
||||
pub mask: sockaddr,
|
||||
}
|
||||
|
||||
ioctl!(readwrite ctliocginfo with 'N', 3; ctl_info);
|
||||
|
||||
ioctl!(write siocsifflags with 'i', 16; ifreq);
|
||||
ioctl!(readwrite siocgifflags with 'i', 17; ifreq);
|
||||
|
||||
ioctl!(write siocsifaddr with 'i', 12; ifreq);
|
||||
ioctl!(readwrite siocgifaddr with 'i', 33; ifreq);
|
||||
|
||||
ioctl!(write siocsifdstaddr with 'i', 14; ifreq);
|
||||
ioctl!(readwrite siocgifdstaddr with 'i', 34; ifreq);
|
||||
|
||||
ioctl!(write siocsifbrdaddr with 'i', 19; ifreq);
|
||||
ioctl!(readwrite siocgifbrdaddr with 'i', 35; ifreq);
|
||||
|
||||
ioctl!(write siocsifnetmask with 'i', 22; ifreq);
|
||||
ioctl!(readwrite siocgifnetmask with 'i', 37; ifreq);
|
||||
|
||||
ioctl!(write siocsifmtu with 'i', 52; ifreq);
|
||||
ioctl!(readwrite siocgifmtu with 'i', 51; ifreq);
|
||||
|
||||
ioctl!(write siocaifaddr with 'i', 26; ifaliasreq);
|
||||
ioctl!(write siocdifaddr with 'i', 25; ifreq);
|
||||
@@ -1,70 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! Platform specific modules.
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod posix;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::linux::{create, Configuration, Device, Queue};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::macos::{create, Configuration, Device, Queue};
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
pub mod ios;
|
||||
#[cfg(target_os = "ios")]
|
||||
pub use self::ios::{create, Configuration, Device, Queue};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod android;
|
||||
#[cfg(target_os = "android")]
|
||||
pub use self::android::{create, Configuration, Device, Queue};
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::configuration::Configuration;
|
||||
use crate::device::Device;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
let dev = super::create(
|
||||
Configuration::default()
|
||||
.name("utun6")
|
||||
.address("192.168.50.1")
|
||||
.netmask("255.255.0.0")
|
||||
.mtu(1400)
|
||||
.up(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
"192.168.50.1".parse::<Ipv4Addr>().unwrap(),
|
||||
dev.address().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"255.255.0.0".parse::<Ipv4Addr>().unwrap(),
|
||||
dev.netmask().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(1400, dev.mtu().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
|
||||
use crate::error::*;
|
||||
use libc::{self, fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
|
||||
|
||||
/// POSIX file descriptor support for `io` traits.
|
||||
pub struct Fd(pub RawFd);
|
||||
|
||||
impl Fd {
|
||||
pub fn new(value: RawFd) -> Result<Self> {
|
||||
if value < 0 {
|
||||
return Err(Error::InvalidDescriptor);
|
||||
}
|
||||
|
||||
Ok(Fd(value))
|
||||
}
|
||||
|
||||
/// Enable non-blocking mode
|
||||
pub fn set_nonblock(&self) -> io::Result<()> {
|
||||
match unsafe { fcntl(self.0, F_SETFL, fcntl(self.0, F_GETFL) | O_NONBLOCK) } {
|
||||
0 => Ok(()),
|
||||
_ => Err(io::Error::last_os_error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Fd {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let amount = libc::read(self.0, buf.as_mut_ptr() as *mut _, buf.len());
|
||||
|
||||
if amount < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(amount as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let iov = bufs.as_ptr().cast();
|
||||
let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _;
|
||||
|
||||
let n = libc::readv(self.0, iov, iovcnt);
|
||||
if n < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Fd {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let amount = libc::write(self.0, buf.as_ptr() as *const _, buf.len());
|
||||
|
||||
if amount < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(amount as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let iov = bufs.as_ptr().cast();
|
||||
let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _;
|
||||
|
||||
let n = libc::writev(self.0, iov, iovcnt);
|
||||
if n < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Fd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for Fd {
|
||||
fn into_raw_fd(mut self) -> RawFd {
|
||||
let fd = self.0;
|
||||
self.0 = -1;
|
||||
fd
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fd {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self.0 >= 0 {
|
||||
libc::close(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
//! POSIX compliant support.
|
||||
|
||||
mod sockaddr;
|
||||
pub use self::sockaddr::SockAddr;
|
||||
|
||||
mod fd;
|
||||
pub use self::fd::Fd;
|
||||
|
||||
mod split;
|
||||
pub use self::split::{Reader, Writer};
|
||||
@@ -1,96 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
use libc::c_uchar;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use libc::c_ushort;
|
||||
|
||||
use libc::AF_INET as _AF_INET;
|
||||
use libc::{in_addr, sockaddr, sockaddr_in};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
/// A wrapper for `sockaddr_in`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SockAddr(sockaddr_in);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
const AF_INET: c_ushort = _AF_INET as c_ushort;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
const AF_INET: c_uchar = _AF_INET as c_uchar;
|
||||
|
||||
impl SockAddr {
|
||||
/// Create a new `SockAddr` from a generic `sockaddr`.
|
||||
pub fn new(value: &sockaddr) -> Result<Self> {
|
||||
if value.sa_family != AF_INET {
|
||||
return Err(Error::InvalidAddress);
|
||||
}
|
||||
|
||||
unsafe { Self::unchecked(value) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Create a new `SockAddr` and not check the source.
|
||||
pub unsafe fn unchecked(value: &sockaddr) -> Result<Self> {
|
||||
Ok(SockAddr(ptr::read(value as *const _ as *const _)))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Get a generic pointer to the `SockAddr`.
|
||||
pub unsafe fn as_ptr(&self) -> *const sockaddr {
|
||||
&self.0 as *const _ as *const sockaddr
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for SockAddr {
|
||||
fn from(ip: Ipv4Addr) -> SockAddr {
|
||||
let octets = ip.octets();
|
||||
let mut addr = unsafe { mem::zeroed::<sockaddr_in>() };
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = 0;
|
||||
addr.sin_addr = in_addr {
|
||||
s_addr: u32::from_ne_bytes(octets),
|
||||
};
|
||||
|
||||
SockAddr(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SockAddr> for Ipv4Addr {
|
||||
fn from(addr: SockAddr) -> Ipv4Addr {
|
||||
let ip = addr.0.sin_addr.s_addr;
|
||||
let [a, b, c, d] = ip.to_ne_bytes();
|
||||
|
||||
Ipv4Addr::new(a, b, c, d)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SockAddr> for sockaddr {
|
||||
fn from(addr: SockAddr) -> sockaddr {
|
||||
unsafe { mem::transmute(addr.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SockAddr> for sockaddr_in {
|
||||
fn from(addr: SockAddr) -> sockaddr_in {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyleft (ↄ) meh. <[email protected]> | http://meh.schizofreni.co
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::platform::posix::Fd;
|
||||
use libc;
|
||||
|
||||
/// Read-only end for a file descriptor.
|
||||
pub struct Reader(pub(crate) Arc<Fd>);
|
||||
|
||||
/// Write-only end for a file descriptor.
|
||||
pub struct Writer(pub(crate) Arc<Fd>);
|
||||
|
||||
impl Read for Reader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let amount = libc::read(self.0.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len());
|
||||
|
||||
if amount < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(amount as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let mut msg: libc::msghdr = mem::zeroed();
|
||||
// msg.msg_name: NULL
|
||||
// msg.msg_namelen: 0
|
||||
msg.msg_iov = bufs.as_mut_ptr().cast();
|
||||
msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _;
|
||||
|
||||
let n = libc::recvmsg(self.0.as_raw_fd(), &mut msg, 0);
|
||||
if n < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let amount = libc::write(self.0.as_raw_fd(), buf.as_ptr() as *const _, buf.len());
|
||||
|
||||
if amount < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(amount as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let mut msg: libc::msghdr = mem::zeroed();
|
||||
// msg.msg_name = NULL
|
||||
// msg.msg_namelen = 0
|
||||
msg.msg_iov = bufs.as_ptr() as *mut _;
|
||||
msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _;
|
||||
|
||||
let n = libc::sendmsg(self.0.as_raw_fd(), &msg, 0);
|
||||
if n < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Reader {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Writer {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/// 使用 https://github.com/spa5k/is_sudo/blob/main/src/window.rs
|
||||
use std::io::Error;
|
||||
use std::ptr;
|
||||
|
||||
use winapi::um::handleapi::CloseHandle;
|
||||
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
|
||||
use winapi::um::securitybaseapi::GetTokenInformation;
|
||||
use winapi::um::winnt::{HANDLE, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation};
|
||||
|
||||
// Use std::io::Error::last_os_error for errors.
|
||||
// NOTE: For this example I'm simple passing on the OS error.
|
||||
// However, customising the error could provide more context
|
||||
|
||||
/// Returns true if the current process has admin rights, otherwise false.
|
||||
pub fn is_app_elevated() -> bool {
|
||||
_is_app_elevated().unwrap_or(false)
|
||||
}
|
||||
|
||||
/// On success returns a bool indicating if the current process has admin rights.
|
||||
/// Otherwise returns an OS error.
|
||||
///
|
||||
/// This is unlikely to fail but if it does it's even more unlikely that you have admin permissions anyway.
|
||||
/// Therefore the public function above simply eats the error and returns a bool.
|
||||
fn _is_app_elevated() -> Result<bool, Error> {
|
||||
let token = QueryAccessToken::from_current_process()?;
|
||||
token.is_elevated()
|
||||
}
|
||||
|
||||
/// A safe wrapper around querying Windows access tokens.
|
||||
pub struct QueryAccessToken(HANDLE);
|
||||
|
||||
impl QueryAccessToken {
|
||||
pub fn from_current_process() -> Result<Self, Error> {
|
||||
unsafe {
|
||||
let mut handle: HANDLE = ptr::null_mut();
|
||||
let result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle);
|
||||
|
||||
if result != 0 {
|
||||
Ok(Self(handle))
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On success returns a bool indicating if the access token has elevated privilidges.
|
||||
/// Otherwise returns an OS error.
|
||||
pub fn is_elevated(&self) -> Result<bool, Error> {
|
||||
unsafe {
|
||||
let mut elevation = TOKEN_ELEVATION::default();
|
||||
let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
|
||||
let mut ret_size = size;
|
||||
// The weird looking repetition of `as *mut _` is casting the reference to a c_void pointer.
|
||||
if GetTokenInformation(
|
||||
self.0,
|
||||
TokenElevation,
|
||||
&mut elevation as *mut _ as *mut _,
|
||||
size,
|
||||
&mut ret_size,
|
||||
) != 0
|
||||
{
|
||||
Ok(elevation.TokenIsElevated != 0)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for QueryAccessToken {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe { CloseHandle(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use ipnet::Ipv4Net;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use vnt_core::context::config::Config;
|
||||
use vnt_core::nat::NetInput;
|
||||
use vnt_core::tls::verifier::CertValidationMode;
|
||||
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
|
||||
use vnt_ipc as vnt_core;
|
||||
use vnt_ipc::port_mapping::PortMapping;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
pub struct FileConfig {
|
||||
pub server: Option<Vec<String>>,
|
||||
pub network_code: Option<String>,
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
pub no_punch: Option<bool>,
|
||||
pub rtx: Option<bool>,
|
||||
pub compress: Option<bool>,
|
||||
pub fec: Option<bool>,
|
||||
pub input: Option<Vec<NetInput>>,
|
||||
pub output: Option<Vec<Ipv4Net>>,
|
||||
pub no_nat: Option<bool>,
|
||||
pub no_tun: Option<bool>,
|
||||
pub mtu: Option<u16>,
|
||||
pub ctrl_port: Option<u16>,
|
||||
pub port_mapping: Option<Vec<String>>,
|
||||
pub allow_mapping: Option<bool>,
|
||||
pub device_id: Option<String>,
|
||||
pub device_name: Option<String>,
|
||||
pub tun_name: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub cert_mode: Option<String>,
|
||||
pub udp_stun: Option<Vec<String>>,
|
||||
pub tcp_stun: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl FileConfig {
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str(&content)?)
|
||||
}
|
||||
|
||||
pub fn save(&self, path: &Path) -> anyhow::Result<()> {
|
||||
let content = toml::to_string_pretty(self)?;
|
||||
std::fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_server_addr(&self) -> anyhow::Result<Vec<ProtocolAddress>> {
|
||||
if let Some(server_raw) = &self.server {
|
||||
let mut server_addr = Vec::with_capacity(server_raw.len());
|
||||
for x in server_raw {
|
||||
server_addr.push(
|
||||
x.parse::<ProtocolAddress>()
|
||||
.map_err(|e| anyhow!("invalid server address '{}': {}", x, e))?,
|
||||
)
|
||||
}
|
||||
Ok(server_addr)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
pub fn to_port_mapping(&self) -> anyhow::Result<Vec<PortMapping>> {
|
||||
if let Some(port_mapping_raw) = &self.port_mapping {
|
||||
let mut port_mapping = Vec::with_capacity(port_mapping_raw.len());
|
||||
for x in port_mapping_raw {
|
||||
port_mapping.push(
|
||||
x.parse::<PortMapping>()
|
||||
.map_err(|e| anyhow!("invalid port_mapping '{}': {}", x, e))?,
|
||||
)
|
||||
}
|
||||
Ok(port_mapping)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// 服务器地址 例如 `-s quic://127.0.0.1:29872`, 支持quic/tcp/wss/dynamic
|
||||
#[clap(short, long)]
|
||||
pub server: Vec<ProtocolAddress>,
|
||||
/// 网络编号,相同编号的会组同一个局域网
|
||||
#[clap(short, long)]
|
||||
pub network_code: Option<String>,
|
||||
#[clap(short = 'k', long, hide = true)]
|
||||
pub token: Option<String>,
|
||||
/// 自定义虚拟IP
|
||||
#[clap(long)]
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
/// 启用加密,设置加密密码
|
||||
#[clap(short, long)]
|
||||
pub password: Option<String>,
|
||||
/// 启用quic优化传输
|
||||
#[clap(long)]
|
||||
pub rtx: bool,
|
||||
/// 启用压缩 (LZ4)
|
||||
#[clap(short = 'z', long)]
|
||||
pub compress: bool,
|
||||
/// 启用 FEC 前向纠错,损失一定带宽来提升网络稳定性
|
||||
#[clap(long)]
|
||||
pub fec: bool,
|
||||
/// 入栈监听网段
|
||||
#[clap(short, long)]
|
||||
pub input: Vec<NetInput>,
|
||||
/// 出栈允许网段
|
||||
#[clap(short, long)]
|
||||
pub output: Vec<Ipv4Net>,
|
||||
/// 自定义设备名称
|
||||
#[clap(long, alias = "name")]
|
||||
pub device_name: Option<String>,
|
||||
/// 设备id
|
||||
#[clap(long, alias = "id")]
|
||||
pub device_id: Option<String>,
|
||||
/// 关闭打洞
|
||||
#[clap(long)]
|
||||
pub no_punch: bool,
|
||||
/// 服务端证书验证
|
||||
#[clap(long)]
|
||||
pub cert_mode: Option<CertValidationMode>,
|
||||
/// 虚拟网卡名称
|
||||
#[clap(long)]
|
||||
pub tun_name: Option<String>,
|
||||
/// 关闭内置子网NAT
|
||||
#[clap(long)]
|
||||
pub no_nat: bool,
|
||||
/// 禁用tun,禁用后只能充当流量出口或者进行端口映射,无需管理员权限
|
||||
#[clap(long)]
|
||||
pub no_tun: bool,
|
||||
/// 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||
#[clap(long)]
|
||||
pub port_mapping: Vec<PortMapping>,
|
||||
/// 是否允许作为端口映射出口,开启后其他设备才可使用本设备的ip为"目标虚拟IP"
|
||||
#[clap(long)]
|
||||
pub allow_mapping: bool,
|
||||
/// 设置mtu
|
||||
#[clap(long)]
|
||||
pub mtu: Option<u16>,
|
||||
/// 控制端口,设置0时禁用控制服务
|
||||
#[clap(long)]
|
||||
pub ctrl_port: Option<u16>,
|
||||
/// 读取配置文件
|
||||
#[arg(long)]
|
||||
pub conf: Option<PathBuf>,
|
||||
/// 输出配置文件示例
|
||||
#[clap(long)]
|
||||
pub conf_example: bool,
|
||||
}
|
||||
impl Args {
|
||||
pub fn parse_compatible() -> Self {
|
||||
let mut args = Args::parse();
|
||||
if args.network_code.is_none() {
|
||||
args.network_code = args.token.clone();
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlConfig {
|
||||
pub ctrl_port: Option<u16>,
|
||||
}
|
||||
|
||||
pub fn build_config_from_args_and_file(
|
||||
args: Option<Args>,
|
||||
file: Option<FileConfig>,
|
||||
) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||
match (args, file) {
|
||||
(Some(args), Some(file)) => build_from_args_and_file(args, file),
|
||||
(Some(args), None) => build_from_args_only(args),
|
||||
(None, Some(file)) => build_from_file_only(file),
|
||||
(None, None) => Err(anyhow!("neither args nor config file provided")),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_from_args_and_file(args: Args, file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||
let server_addr = if args.server.is_empty() {
|
||||
file.to_server_addr()?
|
||||
} else {
|
||||
args.server
|
||||
};
|
||||
let port_mapping = if args.port_mapping.is_empty() {
|
||||
file.to_port_mapping()?
|
||||
} else {
|
||||
args.port_mapping
|
||||
};
|
||||
|
||||
let network_code = args
|
||||
.network_code
|
||||
.or_else(|| file.network_code.clone())
|
||||
.ok_or_else(|| anyhow!("network_code is required"))?;
|
||||
|
||||
let cert_mode = args
|
||||
.cert_mode
|
||||
.or_else(|| file.cert_mode.as_deref().and_then(|s| s.parse().ok()))
|
||||
.unwrap_or(CertValidationMode::InsecureSkipVerification);
|
||||
|
||||
let device_id = match args.device_id.or_else(|| file.device_id.clone()) {
|
||||
Some(id) => id,
|
||||
None => vnt_core::utils::device_id::get_device_id()?,
|
||||
};
|
||||
|
||||
let input = if args.input.is_empty() {
|
||||
file.input.unwrap_or_default()
|
||||
} else {
|
||||
args.input
|
||||
};
|
||||
|
||||
let output = if args.output.is_empty() {
|
||||
file.output.unwrap_or_default()
|
||||
} else {
|
||||
args.output
|
||||
};
|
||||
let mut udp_stun = file.udp_stun.unwrap_or_default();
|
||||
for x in udp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
let mut tcp_stun = file.tcp_stun.unwrap_or_default();
|
||||
for x in tcp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config {
|
||||
server_addr,
|
||||
network_code,
|
||||
ip: args.ip.or(file.ip),
|
||||
no_punch: args.no_punch || file.no_punch.unwrap_or(false),
|
||||
rtx: args.rtx || file.rtx.unwrap_or(false),
|
||||
compress: args.compress || file.compress.unwrap_or(false),
|
||||
fec: args.fec || file.fec.unwrap_or(false),
|
||||
device_id,
|
||||
device_name: args
|
||||
.device_name
|
||||
.or_else(|| file.device_name.clone())
|
||||
.unwrap_or_else(default_hostname),
|
||||
tun_name: args.tun_name.or_else(|| file.tun_name.clone()),
|
||||
password: args.password.or_else(|| file.password.clone()),
|
||||
cert_mode,
|
||||
input,
|
||||
output,
|
||||
no_nat: args.no_nat || file.no_nat.unwrap_or(false),
|
||||
no_tun: args.no_tun || file.no_tun.unwrap_or(false),
|
||||
mtu: args.mtu.or(file.mtu),
|
||||
port_mapping,
|
||||
allow_port_mapping: args.allow_mapping || file.allow_mapping.unwrap_or(false),
|
||||
udp_stun,
|
||||
tcp_stun,
|
||||
};
|
||||
|
||||
let ctrl_config = CtrlConfig {
|
||||
ctrl_port: args.ctrl_port.or(file.ctrl_port),
|
||||
};
|
||||
Ok((config, ctrl_config))
|
||||
}
|
||||
|
||||
fn build_from_args_only(args: Args) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||
let device_id = match args.device_id {
|
||||
Some(id) => id,
|
||||
None => vnt_core::utils::device_id::get_device_id()?,
|
||||
};
|
||||
let config = Config {
|
||||
server_addr: args.server,
|
||||
network_code: args
|
||||
.network_code
|
||||
.ok_or_else(|| anyhow!("network_code is required"))?,
|
||||
ip: args.ip,
|
||||
no_punch: args.no_punch,
|
||||
rtx: args.rtx,
|
||||
input: args.input,
|
||||
compress: args.compress,
|
||||
fec: args.fec,
|
||||
device_id,
|
||||
device_name: args.device_name.unwrap_or_else(default_hostname),
|
||||
tun_name: args.tun_name,
|
||||
password: args.password,
|
||||
cert_mode: args
|
||||
.cert_mode
|
||||
.unwrap_or(CertValidationMode::InsecureSkipVerification),
|
||||
output: args.output,
|
||||
no_nat: args.no_nat,
|
||||
no_tun: args.no_tun,
|
||||
mtu: args.mtu,
|
||||
port_mapping: args.port_mapping,
|
||||
allow_port_mapping: args.allow_mapping,
|
||||
..Default::default()
|
||||
};
|
||||
let ctrl_config = CtrlConfig {
|
||||
ctrl_port: args.ctrl_port,
|
||||
};
|
||||
Ok((config, ctrl_config))
|
||||
}
|
||||
|
||||
fn build_from_file_only(file: FileConfig) -> anyhow::Result<(Config, CtrlConfig)> {
|
||||
let server_addr = file.to_server_addr()?;
|
||||
let port_mapping = file.to_port_mapping()?;
|
||||
|
||||
let cert_mode = file
|
||||
.cert_mode
|
||||
.as_deref()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(CertValidationMode::InsecureSkipVerification);
|
||||
|
||||
let device_id = match file.device_id.clone() {
|
||||
Some(id) => id,
|
||||
None => vnt_core::utils::device_id::get_device_id()?,
|
||||
};
|
||||
let mut udp_stun = file.udp_stun.unwrap_or_default();
|
||||
for x in udp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
let mut tcp_stun = file.tcp_stun.unwrap_or_default();
|
||||
for x in tcp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config {
|
||||
server_addr,
|
||||
network_code: file
|
||||
.network_code
|
||||
.ok_or_else(|| anyhow!("network_code is required"))?,
|
||||
ip: file.ip,
|
||||
no_punch: file.no_punch.unwrap_or(false),
|
||||
rtx: file.rtx.unwrap_or(false),
|
||||
input: file.input.unwrap_or_default(),
|
||||
compress: file.compress.unwrap_or(false),
|
||||
fec: file.fec.unwrap_or(false),
|
||||
device_id,
|
||||
device_name: file.device_name.clone().unwrap_or_else(default_hostname),
|
||||
tun_name: file.tun_name.clone(),
|
||||
password: file.password.clone(),
|
||||
cert_mode,
|
||||
output: file.output.unwrap_or_default(),
|
||||
no_nat: file.no_nat.unwrap_or(false),
|
||||
no_tun: file.no_tun.unwrap_or(false),
|
||||
mtu: file.mtu,
|
||||
port_mapping,
|
||||
allow_port_mapping: file.allow_mapping.unwrap_or(false),
|
||||
udp_stun,
|
||||
tcp_stun,
|
||||
};
|
||||
let ctrl_config = CtrlConfig {
|
||||
ctrl_port: file.ctrl_port,
|
||||
};
|
||||
Ok((config, ctrl_config))
|
||||
}
|
||||
|
||||
fn default_hostname() -> String {
|
||||
hostname::get()
|
||||
.ok()
|
||||
.and_then(|v| v.into_string().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
impl FileConfig {
|
||||
pub fn print_example(path: Option<&Path>) -> anyhow::Result<()> {
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let example = format!(
|
||||
r#"# ==================================
|
||||
# VNT 配置文件示例(程序版本 v{version})
|
||||
# ==================================
|
||||
|
||||
# --- 网络配置 ---
|
||||
# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)
|
||||
network_code = "your_network_code"
|
||||
|
||||
# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)
|
||||
# dynamic 协议使用dns txt解析记录值
|
||||
server = ["quic://1.2.3.4:29872"]
|
||||
|
||||
# ===简单使用以下参数可以不动===
|
||||
|
||||
# 自定义虚拟 IP (可选)
|
||||
# ip = "10.10.0.2"
|
||||
|
||||
# 是否启用quic优化传输 (默认 false,设置为true时开启)
|
||||
# rtx = false
|
||||
|
||||
# 是否启用 FEC 前向纠错,损失一定带宽来提升网络稳定性(默认 false,设置为true时开启)
|
||||
# fec = false
|
||||
|
||||
# 是否关闭 P2P 打洞 (默认 false,设置为true时关闭)
|
||||
# no_punch = false
|
||||
|
||||
# 是否启用 LZ4 压缩 (默认 false,设置为true时开启)
|
||||
# compress = false
|
||||
|
||||
# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点
|
||||
# input = ["192.168.0.0/24,10.26.0.2", "192.168.1.0/24,10.26.0.3"]
|
||||
|
||||
# 出栈允许网段,用于点对网,允许指定网段的转发
|
||||
# output = ["0.0.0.0/0"]
|
||||
|
||||
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
||||
# no_nat = false
|
||||
|
||||
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
|
||||
# no_tun = false
|
||||
|
||||
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
||||
# 例如 port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"]
|
||||
# tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 则表示将本地tcp的81端口的数据转发到10.0.0.2:80
|
||||
# tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80
|
||||
# tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80
|
||||
# port_mapping = []
|
||||
|
||||
# 是否允许作为端口映射出口,开启(设置为true)后其他设备才可使用本设备的ip为"目标虚拟IP"
|
||||
# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络
|
||||
# allow_mapping = false
|
||||
|
||||
# 控制服务的 tcp 端口
|
||||
# ctrl_port = 11233
|
||||
|
||||
# MTU 设置
|
||||
# mtu = 1400
|
||||
|
||||
# --- 设备配置 ---
|
||||
|
||||
# 设备名称 (可选,默认读取本机 hostname)
|
||||
# device_name = "my-device"
|
||||
|
||||
# 设备 ID (可选,不填自动生成,不同设备ID不能相同)
|
||||
# device_id = "device-id-xxxx"
|
||||
|
||||
# 虚拟网卡名称
|
||||
# tun_name = "vnt-tun"
|
||||
|
||||
# --- 安全配置 ---
|
||||
|
||||
# 加密密码 (可选)
|
||||
# password = "123456"
|
||||
|
||||
# 证书校验方式:
|
||||
# skip 跳过验证(默认)
|
||||
# standard 使用系统证书验证
|
||||
# finger 使用证书指纹验证,服务端启动时日志会输出指纹,
|
||||
# 例如 finger:3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1
|
||||
# cert_mode = "skip"
|
||||
|
||||
# --- 其他配置 ---
|
||||
# 自定义stun地址,分别用于udp打洞和tcp打洞,需要单独配置,不设置则用默认stun
|
||||
# udp_stun = ["stun.chat.bilibili.com"]
|
||||
# tcp_stun = ["stun.nextcloud.com:443"]
|
||||
"#,
|
||||
version = VERSION
|
||||
);
|
||||
println!("--- 示例配置文件内容 ---\n{}", example);
|
||||
if let Some(p) = path {
|
||||
std::fs::write(p, &example)?;
|
||||
println!("示例配置文件已写入 {}", p.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use crossbeam::channel::RecvError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("packet error")]
|
||||
PacketError(#[from] packet::error::Error),
|
||||
#[error("Io error")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Channel error")]
|
||||
Channel(#[from] RecvError),
|
||||
#[error("Protobuf error")]
|
||||
Protobuf(#[from] protobuf::Error),
|
||||
#[error("Invalid packet")]
|
||||
InvalidPacket,
|
||||
#[error("Not support")]
|
||||
NotSupport,
|
||||
#[error("Stop")]
|
||||
Stop(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const WINTUN_DLL: &[u8] = include_bytes!("../dll/amd64/wintun.dll");
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const WINTUN_DLL: &[u8] = include_bytes!("../dll/x86/wintun.dll");
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const WINTUN_DLL: &[u8] = include_bytes!("../dll/arm64/wintun.dll");
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
const WINTUN_DLL: &[u8] = include_bytes!("../dll/arm/wintun.dll");
|
||||
|
||||
pub fn extract_wintun() {
|
||||
if let Err(e) = extract_wintun_impl() {
|
||||
log::error!("extract wintun.dll {:?}", e);
|
||||
}
|
||||
}
|
||||
fn extract_wintun_impl() -> io::Result<()> {
|
||||
let path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join("wintun.dll")))
|
||||
.unwrap_or_else(|| Path::new("wintun.dll").to_path_buf());
|
||||
|
||||
if !path.exists() {
|
||||
let mut file = fs::File::create(&path)?;
|
||||
file.write_all(WINTUN_DLL)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Local;
|
||||
|
||||
use crate::DEVICE_LIST;
|
||||
use crate::error::*;
|
||||
use crate::handle::DIRECT_ROUTE_TABLE;
|
||||
use crate::protocol::{control_packet, NetPacket, Protocol, Version};
|
||||
use crate::protocol::control_packet::PingPacket;
|
||||
|
||||
pub fn handle_loop(udp: UdpSocket, server_addr: SocketAddr) -> Result<()> {
|
||||
const INTERVAL: u64 = 3000;
|
||||
const MAX_INTERVAL: i64 = 3000 * 3;
|
||||
let mut buf = [0u8; (4 + 8 + 4)];
|
||||
let mut net_packet = NetPacket::new(&mut buf)?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::Control);
|
||||
net_packet.set_transport_protocol(control_packet::Protocol::Ping.into());
|
||||
net_packet.set_ttl(255);
|
||||
loop {
|
||||
let current_time = Local::now().timestamp_millis();
|
||||
{
|
||||
let mut ping = PingPacket::new(net_packet.payload_mut())?;
|
||||
ping.set_time(current_time);
|
||||
let epoch = { DEVICE_LIST.lock().0 };
|
||||
ping.set_epoch(epoch);
|
||||
}
|
||||
let _ = udp.send_to(net_packet.buffer(), server_addr);
|
||||
for x in DIRECT_ROUTE_TABLE.iter() {
|
||||
let virtual_ip = x.key().clone();
|
||||
let route = x.value().clone();
|
||||
drop(x);
|
||||
if current_time - route.recv_time <= MAX_INTERVAL {
|
||||
let _ = udp.send_to(net_packet.buffer(), route.address);
|
||||
} else {
|
||||
DIRECT_ROUTE_TABLE.remove_if(&virtual_ip, |_, route| {
|
||||
current_time - route.recv_time <= MAX_INTERVAL
|
||||
});
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(INTERVAL));
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
use std::net::{ Ipv4Addr, SocketAddr};
|
||||
use std::sync::atomic::AtomicI64;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Local;
|
||||
use dashmap::DashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use moka::sync::Cache;
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
|
||||
use crate::proto::message::NatType;
|
||||
|
||||
pub mod heartbeat_handler;
|
||||
pub mod punch_handler;
|
||||
pub mod registration_handler;
|
||||
pub mod tun_handler;
|
||||
pub mod udp_recv_handler;
|
||||
lazy_static! {
|
||||
/// 0. 机器纪元,每一次上线或者下线都会增1,由服务端维护,用于感知网络中机器变化
|
||||
/// 服务端和客户端的不一致,则服务端会推送新的设备列表
|
||||
/// 1. 网络中的虚拟ip列表
|
||||
pub static ref DEVICE_LIST:Mutex<(u32,Vec<Ipv4Addr>)> = const_mutex((0,Vec::new()));
|
||||
/// 服务器延迟
|
||||
pub static ref SERVER_RT:AtomicI64 = AtomicI64::new(-1);
|
||||
/// id
|
||||
pub static ref ID:AtomicI64 = AtomicI64::new(0);
|
||||
/// 直连路由表
|
||||
pub static ref DIRECT_ROUTE_TABLE:DashMap<Ipv4Addr,Route> = DashMap::new();
|
||||
/// 地址映射
|
||||
pub static ref ADDR_TABLE:Cache<SocketAddr,Ipv4Addr> = Cache::builder()
|
||||
.time_to_idle(Duration::from_secs(60*5)).build();
|
||||
/// 当前设备的nat信息
|
||||
pub static ref NAT_INFO:Mutex<Option<NatInfo>> = const_mutex(None);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NatInfo {
|
||||
public_ips: Vec<u32>,
|
||||
public_port: u16,
|
||||
public_port_range: u16,
|
||||
nat_type: NatType,
|
||||
}
|
||||
|
||||
impl NatInfo {
|
||||
pub fn new(public_ips: Vec<u32>,
|
||||
public_port: u16,
|
||||
public_port_range: u16,
|
||||
nat_type: NatType, ) -> Self {
|
||||
Self {
|
||||
public_ips,
|
||||
public_port,
|
||||
public_port_range,
|
||||
nat_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化nat信息
|
||||
pub fn init_nat_info(public_ip: u32, public_port: u16) {
|
||||
match crate::nat::check::public_ip_list() {
|
||||
Ok((nat_type, ips, port_range)) => {
|
||||
let mut public_ips = Vec::new();
|
||||
public_ips.push(public_ip);
|
||||
for ip in ips {
|
||||
let ip = u32::from_be_bytes(ip.octets());
|
||||
if ip != public_ip {
|
||||
public_ips.push(ip);
|
||||
}
|
||||
}
|
||||
let nat_info = NatInfo::new(public_ips,
|
||||
public_port,
|
||||
port_range, nat_type);
|
||||
// println!("nat信息:{:?}",nat_info);
|
||||
let mut nat_info_lock = NAT_INFO.lock();
|
||||
nat_info_lock.replace(nat_info);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("获取nat数据失败,将无法进行udp打洞:{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CurrentDeviceInfo {
|
||||
pub(crate) virtual_ip: Ipv4Addr,
|
||||
pub(crate) virtual_gateway: Ipv4Addr,
|
||||
pub(crate) virtual_netmask: Ipv4Addr,
|
||||
//网络地址
|
||||
pub(crate) virtual_network: Ipv4Addr,
|
||||
//直接广播地址
|
||||
pub(crate) broadcast_address: Ipv4Addr,
|
||||
//链接的服务器地址
|
||||
pub(crate) connect_server: SocketAddr,
|
||||
}
|
||||
|
||||
impl CurrentDeviceInfo {
|
||||
pub fn new(virtual_ip: Ipv4Addr, virtual_gateway: Ipv4Addr, virtual_netmask: Ipv4Addr, connect_server: SocketAddr) -> Self {
|
||||
let broadcast_address = (!u32::from_be_bytes(virtual_netmask.octets()))
|
||||
| u32::from_be_bytes(virtual_gateway.octets());
|
||||
let broadcast_address = Ipv4Addr::from(broadcast_address);
|
||||
let virtual_network = u32::from_be_bytes(virtual_netmask.octets())
|
||||
& u32::from_be_bytes(virtual_gateway.octets());
|
||||
let virtual_network = Ipv4Addr::from(virtual_network);
|
||||
Self {
|
||||
virtual_ip,
|
||||
virtual_netmask,
|
||||
virtual_gateway,
|
||||
virtual_network,
|
||||
broadcast_address,
|
||||
connect_server,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Route {
|
||||
pub(crate) address: SocketAddr,
|
||||
//用心跳探测延迟,收包时更新
|
||||
pub(crate) delay: i64,
|
||||
//收包时更新,如果太久没有收到消息则剔除
|
||||
pub(crate) recv_time: i64,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(address: SocketAddr) -> Self {
|
||||
Self {
|
||||
address,
|
||||
delay: -1,
|
||||
recv_time: Local::now().timestamp_millis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam::channel::{Receiver, RecvTimeoutError, Sender, SendError, TrySendError};
|
||||
use dashmap::DashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use protobuf::Message;
|
||||
|
||||
use crate::{CurrentDeviceInfo, DEVICE_LIST, NAT_INFO, NatInfo};
|
||||
use crate::error::*;
|
||||
use crate::handle::DIRECT_ROUTE_TABLE;
|
||||
use crate::proto::message::{NatType, Punch, Step};
|
||||
use crate::protocol::{control_packet, NetPacket, Protocol, turn_packet, Version};
|
||||
use crate::protocol::control_packet::PunchRequestPacket;
|
||||
use crate::protocol::turn_packet::TurnPacket;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref STEP_MAP:DashMap<Ipv4Addr,Step> = DashMap::new();
|
||||
}
|
||||
/// 每一种类型一个通道,减少相互干扰
|
||||
pub fn bounded() -> (PunchSender, ConeReceiver, ReqSymmetricReceiver, ResSymmetricReceiver) {
|
||||
let (cone_sender, cone_receiver) = crossbeam::channel::bounded(3);
|
||||
let (req_symmetric_sender, req_symmetric_receiver) = crossbeam::channel::bounded(1);
|
||||
let (res_symmetric_sender, res_symmetric_receiver) = crossbeam::channel::bounded(1);
|
||||
(PunchSender::new(cone_sender, req_symmetric_sender, res_symmetric_sender),
|
||||
ConeReceiver(cone_receiver), ReqSymmetricReceiver(req_symmetric_receiver),
|
||||
ResSymmetricReceiver(res_symmetric_receiver))
|
||||
}
|
||||
|
||||
pub struct ConeReceiver(Receiver<Punch>);
|
||||
|
||||
pub struct ReqSymmetricReceiver(Receiver<Punch>);
|
||||
|
||||
pub struct ResSymmetricReceiver(Receiver<Punch>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PunchSender {
|
||||
cone_sender: Sender<Punch>,
|
||||
req_symmetric_sender: Sender<Punch>,
|
||||
res_symmetric_sender: Sender<Punch>,
|
||||
}
|
||||
|
||||
impl PunchSender {
|
||||
pub fn new(cone_sender: Sender<Punch>,
|
||||
req_symmetric_sender: Sender<Punch>,
|
||||
res_symmetric_sender: Sender<Punch>, ) -> Self {
|
||||
Self {
|
||||
cone_sender,
|
||||
req_symmetric_sender,
|
||||
res_symmetric_sender,
|
||||
}
|
||||
}
|
||||
pub fn send(&self, punch: Punch) -> std::result::Result<(), SendError<Punch>> {
|
||||
match punch.nat_type.enum_value_or_default() {
|
||||
NatType::Symmetric => {
|
||||
if punch.reply {
|
||||
// 为true表示回应,也就是主动发起的打洞操作
|
||||
self.res_symmetric_sender.send(punch)
|
||||
} else {
|
||||
self.req_symmetric_sender.send(punch)
|
||||
}
|
||||
}
|
||||
NatType::Cone => {
|
||||
self.cone_sender.send(punch)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn try_send(&self, punch: Punch) -> std::result::Result<(), TrySendError<Punch>> {
|
||||
match punch.nat_type.enum_value_or_default() {
|
||||
NatType::Symmetric => {
|
||||
if punch.reply {
|
||||
// 为true表示回应,也就是主动发起的打洞操作
|
||||
self.res_symmetric_sender.try_send(punch)
|
||||
} else {
|
||||
self.req_symmetric_sender.try_send(punch)
|
||||
}
|
||||
}
|
||||
NatType::Cone => {
|
||||
self.cone_sender.try_send(punch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(udp: &UdpSocket, punch_list: Vec<Punch>, buf: &[u8]) -> Result<()> {
|
||||
let mut counter = 0u64;
|
||||
for punch in punch_list {
|
||||
let dest = Ipv4Addr::from(punch.virtual_ip);
|
||||
if DIRECT_ROUTE_TABLE.contains_key(&dest) {
|
||||
continue;
|
||||
}
|
||||
// println!("punch {:?}", punch);
|
||||
match punch.nat_type.enum_value_or_default() {
|
||||
NatType::Symmetric => {
|
||||
match punch.step.enum_value_or_default() {
|
||||
Step::Step1 | Step::Step2 | Step::Step3 => {
|
||||
//预测范围发送
|
||||
for pub_ip in punch.public_ip_list {
|
||||
let pub_ip = Ipv4Addr::from(pub_ip);
|
||||
for range in 0..punch.public_port_range + 1 {
|
||||
let right_port = ((punch.public_port + range) & 0xFFFF) as u16;
|
||||
let left_port = ((0xFFFF + punch.public_port - range) & 0xFFFF) as u16;
|
||||
if right_port != 0 {
|
||||
// println!("{:?}", SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)));
|
||||
udp.send_to(
|
||||
buf,
|
||||
SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)),
|
||||
)?;
|
||||
select_sleep(&mut counter);
|
||||
}
|
||||
if left_port != 0 && range != 0 {
|
||||
// println!("{:?}", SocketAddr::V4(SocketAddrV4::new(pub_ip, right_port)));
|
||||
if left_port == right_port {
|
||||
break;
|
||||
}
|
||||
udp.send_to(
|
||||
buf,
|
||||
SocketAddr::V4(SocketAddrV4::new(pub_ip, left_port)),
|
||||
)?;
|
||||
select_sleep(&mut counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Step::Step4 => {
|
||||
//全范围发送
|
||||
for pub_ip in punch.public_ip_list {
|
||||
let pub_ip = Ipv4Addr::from(pub_ip);
|
||||
for port in 1..0xFFFF {
|
||||
udp.send_to(
|
||||
buf,
|
||||
SocketAddr::V4(SocketAddrV4::new(pub_ip, port)),
|
||||
)?;
|
||||
select_sleep(&mut counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NatType::Cone => {
|
||||
for pub_ip in punch.public_ip_list {
|
||||
udp.send_to(
|
||||
buf,
|
||||
SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::from(pub_ip),
|
||||
punch.public_port as u16,
|
||||
)),
|
||||
)?;
|
||||
select_sleep(&mut counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 给对称nat发送打洞数据包
|
||||
pub fn req_symmetric_handle_loop(
|
||||
receiver: ReqSymmetricReceiver,
|
||||
udp: UdpSocket,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let receiver = receiver.0;
|
||||
handle_loop(receiver, udp, cur_info)
|
||||
}
|
||||
|
||||
/// 给对称nat发送打洞数据包,处理主动发起的打洞操作
|
||||
pub fn res_symmetric_handle_loop(
|
||||
receiver: ResSymmetricReceiver,
|
||||
udp: UdpSocket,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let receiver = receiver.0;
|
||||
let mut buf = [0u8; 12];
|
||||
let mut packet = NetPacket::new(&mut buf)?;
|
||||
packet.set_version(Version::V1);
|
||||
packet.set_ttl(255);
|
||||
packet.set_protocol(Protocol::Control);
|
||||
packet.set_transport_protocol(control_packet::Protocol::PunchRequest.into());
|
||||
{
|
||||
let mut punch_packet = PunchRequestPacket::new(packet.payload_mut())?;
|
||||
punch_packet.set_source(cur_info.virtual_ip);
|
||||
}
|
||||
loop {
|
||||
match receiver.recv_timeout(Duration::from_secs(30)) {
|
||||
Ok(punch) => {
|
||||
let mut list = Vec::new();
|
||||
list.push(punch);
|
||||
loop {
|
||||
match receiver.try_recv() {
|
||||
Ok(punch) => {
|
||||
list.push(punch);
|
||||
}
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for punch in &list {
|
||||
let dest = Ipv4Addr::from(punch.virtual_ip);
|
||||
match punch.step.enum_value_or_default() {
|
||||
Step::Step1 => {
|
||||
STEP_MAP.insert(dest, Step::Step2);
|
||||
}
|
||||
Step::Step2 => {
|
||||
STEP_MAP.insert(dest, Step::Step3);
|
||||
}
|
||||
Step::Step3 => {
|
||||
STEP_MAP.insert(dest, Step::Step4);
|
||||
}
|
||||
Step::Step4 => {
|
||||
STEP_MAP.insert(dest, Step::Step1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(e) = handle(&udp, list, packet.buffer()) {
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
punch_request_handle(&udp, &cur_info)?;
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(Error::Stop("打洞线程通道关闭".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 给锥形nat发送打洞数据包
|
||||
pub fn cone_handle_loop(
|
||||
receiver: ConeReceiver,
|
||||
udp: UdpSocket,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let receiver = receiver.0;
|
||||
handle_loop(receiver, udp, cur_info)
|
||||
}
|
||||
|
||||
pub fn handle_loop(
|
||||
receiver: Receiver<Punch>,
|
||||
udp: UdpSocket,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let mut buf = [0u8; 12];
|
||||
let mut packet = NetPacket::new(&mut buf)?;
|
||||
packet.set_version(Version::V1);
|
||||
packet.set_ttl(255);
|
||||
packet.set_protocol(Protocol::Control);
|
||||
packet.set_transport_protocol(control_packet::Protocol::PunchRequest.into());
|
||||
{
|
||||
let mut punch_packet = PunchRequestPacket::new(packet.payload_mut())?;
|
||||
punch_packet.set_source(cur_info.virtual_ip);
|
||||
}
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(punch) => {
|
||||
let mut list = Vec::new();
|
||||
list.push(punch);
|
||||
loop {
|
||||
match receiver.try_recv() {
|
||||
Ok(punch) => {
|
||||
list.push(punch);
|
||||
}
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(e) = handle(&udp, list, packet.buffer()) {
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(Error::Stop("打洞线程通道关闭".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_sleep(counter: &mut u64) {
|
||||
*counter += 1;
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
// if *counter > 1 {
|
||||
// if cone_nat {
|
||||
// thread::sleep(Duration::from_millis(2));
|
||||
// } else {
|
||||
// if (*counter) & 10 == 10 {
|
||||
// thread::sleep(Duration::from_millis(1));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
fn punch_request_handle(udp: &UdpSocket, cur_info: &CurrentDeviceInfo) -> Result<()> {
|
||||
let nat_info_lock = NAT_INFO.lock();
|
||||
let nat_info = nat_info_lock.clone();
|
||||
drop(nat_info_lock);
|
||||
if let Some(nat_info) = nat_info {
|
||||
if let Err(e) = send_punch(&udp,
|
||||
&cur_info,
|
||||
nat_info) {
|
||||
println!("发送打洞数据失败 :{:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Stop("未初始化nat信息".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn send_punch(udp: &UdpSocket, cur_info: &CurrentDeviceInfo, nat_info: NatInfo) -> Result<()> {
|
||||
let lock = DEVICE_LIST.lock();
|
||||
let list = lock.1.clone();
|
||||
drop(lock);
|
||||
for ip in list {
|
||||
//只向ip比自己大的发起打洞,避免双方同时发起打洞浪费流量
|
||||
if ip > cur_info.virtual_ip && !DIRECT_ROUTE_TABLE.contains_key(&ip) {
|
||||
let step = if let Some(step) = STEP_MAP.get(&ip) {
|
||||
*step
|
||||
} else {
|
||||
Step::Step1
|
||||
};
|
||||
let bytes = punch_packet(cur_info.virtual_ip,
|
||||
nat_info.clone(), ip, step)?;
|
||||
udp.send_to(&bytes, cur_info.connect_server)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn punch_packet(virtual_ip: Ipv4Addr, nat_info: NatInfo, dest: Ipv4Addr, step: Step) -> Result<Vec<u8>> {
|
||||
let mut punch_reply = Punch::new();
|
||||
punch_reply.reply = false;
|
||||
punch_reply.virtual_ip = u32::from_be_bytes(virtual_ip.octets());
|
||||
punch_reply.step = protobuf::EnumOrUnknown::new(step);
|
||||
punch_reply.public_ip_list = nat_info.public_ips;
|
||||
punch_reply.public_port = nat_info.public_port as u32;
|
||||
punch_reply.public_port_range = nat_info.public_port_range as u32;
|
||||
punch_reply.nat_type = protobuf::EnumOrUnknown::new(nat_info.nat_type);
|
||||
let bytes = punch_reply.write_to_bytes()?;
|
||||
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + bytes.len()])?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::OtherTurn);
|
||||
net_packet.set_transport_protocol(turn_packet::Protocol::Punch.into());
|
||||
net_packet.set_ttl(255);
|
||||
let mut turn_packet = TurnPacket::new(net_packet.payload_mut())?;
|
||||
turn_packet.set_source(virtual_ip);
|
||||
turn_packet.set_destination(dest);
|
||||
turn_packet.set_payload(&bytes);
|
||||
Ok(net_packet.into_buffer())
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
use std::io;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Local;
|
||||
use parking_lot::RwLock;
|
||||
use protobuf::Message;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::proto::message::{RegistrationRequest, RegistrationResponse};
|
||||
use crate::protocol::{error_packet, NetPacket, Protocol, service_packet, Version};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref REQUEST:RwLock<Option<(String,String)>> = parking_lot::const_rwlock(None);
|
||||
static ref REGISTRATION_TIME:AtomicI64=AtomicI64::new(0);
|
||||
}
|
||||
|
||||
///向中继服务器注册,token标识一个虚拟网关,mac_address防止多次注册时得到的ip不一致
|
||||
pub fn registration(
|
||||
udp: &UdpSocket,
|
||||
server_address: SocketAddr,
|
||||
token: String,
|
||||
mac_address: String,
|
||||
) -> Result<RegistrationResponse> {
|
||||
// todo 和服务器通信加密
|
||||
let request_packet = registration_request_packet(token.clone(), mac_address.clone())?;
|
||||
let buf = request_packet.buffer();
|
||||
let mut counter = 0;
|
||||
let mut recv_buf = [0u8; 10240];
|
||||
udp.set_read_timeout(Some(Duration::from_millis(500)))?;
|
||||
loop {
|
||||
counter += 1;
|
||||
if counter & 10 == 10 {
|
||||
return Err(Error::Stop("注册请求超时".to_string()));
|
||||
}
|
||||
udp.send_to(buf, server_address)?;
|
||||
let (len, addr) = match udp.recv_from(&mut recv_buf) {
|
||||
Ok(ok) => ok,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut {
|
||||
continue;
|
||||
}
|
||||
return Err(Error::Io(e));
|
||||
}
|
||||
};
|
||||
if server_address != addr {
|
||||
continue;
|
||||
}
|
||||
let net_packet = NetPacket::new(&recv_buf[..len])?;
|
||||
match net_packet.protocol() {
|
||||
Protocol::Service => {
|
||||
match service_packet::Protocol::from(net_packet.transport_protocol()) {
|
||||
service_packet::Protocol::RegistrationResponse => {
|
||||
let response =
|
||||
RegistrationResponse::parse_from_bytes(net_packet.payload())?;
|
||||
let _ = REQUEST.write().replace((token, mac_address));
|
||||
udp.set_read_timeout(None)?;
|
||||
return Ok(response);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Protocol::Error => {
|
||||
match error_packet::Protocol::from(net_packet.transport_protocol()) {
|
||||
error_packet::Protocol::TokenError => {
|
||||
return Err(Error::Stop("token错误".to_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn registration_request_packet(token: String, mac_address: String) -> Result<NetPacket<Vec<u8>>> {
|
||||
let mut request = RegistrationRequest::new();
|
||||
request.token = token;
|
||||
request.mac_address = mac_address;
|
||||
let bytes = request.write_to_bytes()?;
|
||||
let buf = vec![0u8; 4 + bytes.len()];
|
||||
let mut net_packet = NetPacket::new(buf)?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::Service);
|
||||
net_packet.set_transport_protocol(service_packet::Protocol::RegistrationRequest.into());
|
||||
net_packet.set_ttl(255);
|
||||
net_packet.set_payload(&bytes);
|
||||
Ok(net_packet)
|
||||
}
|
||||
|
||||
pub fn fast_registration(udp: &UdpSocket, server_address: SocketAddr) -> Result<()> {
|
||||
let last = REGISTRATION_TIME.load(Ordering::Relaxed);
|
||||
let new = Local::now().timestamp_millis();
|
||||
if new - last < 2000
|
||||
|| REGISTRATION_TIME
|
||||
.compare_exchange(last, new, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
//短时间不重复注册
|
||||
return Ok(());
|
||||
}
|
||||
let lock = REQUEST.read();
|
||||
let option = lock.clone();
|
||||
drop(lock);
|
||||
if let Some((token, mac_address)) = option {
|
||||
let request_packet = registration_request_packet(token, mac_address)?;
|
||||
udp.send_to(request_packet.buffer(), server_address)?;
|
||||
REGISTRATION_TIME.store(Local::now().timestamp_millis(), Ordering::Relaxed);
|
||||
return Ok(());
|
||||
}
|
||||
return Err(Error::Stop("注册信息不存在".to_string()));
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/// 接收tun数据,并且转发到udp上
|
||||
use std::net::{IpAddr, Ipv4Addr, UdpSocket};
|
||||
|
||||
use chrono::Local;
|
||||
use packet::icmp::icmp::IcmpPacket;
|
||||
use packet::icmp::Kind;
|
||||
use packet::ip::ipv4;
|
||||
use packet::ip::ipv4::packet::IpV4Packet;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::handle::{CurrentDeviceInfo, DIRECT_ROUTE_TABLE};
|
||||
use crate::protocol::{NetPacket, Protocol, Version};
|
||||
use crate::protocol::turn_packet::TurnPacket;
|
||||
use crate::tun_device::TunReader;
|
||||
|
||||
/// 是否在一个网段
|
||||
fn check_dest(dest: Ipv4Addr, cur_info: &CurrentDeviceInfo) -> bool {
|
||||
u32::from_be_bytes(dest.octets()) & u32::from_be_bytes(cur_info.virtual_netmask.octets())
|
||||
== u32::from_be_bytes(cur_info.virtual_network.octets())
|
||||
}
|
||||
|
||||
fn icmp(udp: &UdpSocket, mut ipv4_packet: IpV4Packet<&mut [u8]>) -> Result<()> {
|
||||
if ipv4_packet.protocol() == ipv4::protocol::Protocol::Icmp {
|
||||
let mut icmp = IcmpPacket::new(ipv4_packet.payload_mut())?;
|
||||
if icmp.kind() == Kind::EchoRequest {
|
||||
icmp.set_kind(Kind::EchoReply);
|
||||
icmp.update_checksum();
|
||||
let src = ipv4_packet.source_ip();
|
||||
ipv4_packet.set_source_ip(ipv4_packet.destination_ip());
|
||||
ipv4_packet.set_destination_ip(src);
|
||||
ipv4_packet.update_checksum();
|
||||
let mut addr = udp.local_addr()?;
|
||||
addr.set_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
udp.send_to(ipv4_packet.buffer, addr)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle(
|
||||
udp: &UdpSocket,
|
||||
data: &mut [u8],
|
||||
cur_info: &CurrentDeviceInfo,
|
||||
net_packet: &mut NetPacket<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
let data_len = data.len();
|
||||
let ipv4_packet = match IpV4Packet::new(data) {
|
||||
Ok(ipv4_packet) => ipv4_packet,
|
||||
Err(packet::error::Error::Unimplemented) => {
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
let src_ip = ipv4_packet.source_ip();
|
||||
let dest_ip = ipv4_packet.destination_ip();
|
||||
// if dest_ip == cur_info.broadcast_address {
|
||||
// // 启动服务后会收到对137端口的广播
|
||||
// // 137端口是在局域网中提供计算机的名字或IP地址查询服务
|
||||
// return Ok(());
|
||||
// }
|
||||
if src_ip != cur_info.virtual_ip || !check_dest(dest_ip, &cur_info) {
|
||||
return Ok(());
|
||||
}
|
||||
if src_ip == dest_ip {
|
||||
return icmp(&udp, ipv4_packet);
|
||||
}
|
||||
let mut ipv4_turn_packet = TurnPacket::new(net_packet.payload_mut())?;
|
||||
ipv4_turn_packet.set_source(src_ip);
|
||||
ipv4_turn_packet.set_destination(dest_ip);
|
||||
ipv4_turn_packet.set_payload(ipv4_packet.buffer);
|
||||
//优先发到直连到地址
|
||||
if let Some(route) = DIRECT_ROUTE_TABLE.get(&dest_ip) {
|
||||
let current_time = Local::now().timestamp_millis();
|
||||
if current_time - route.recv_time < 3_000 {
|
||||
udp.send_to(&net_packet.buffer()[..(4 + 8 + data_len)], route.address)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
udp.send_to(&net_packet.buffer()[..(4 + 8 + data_len)], cur_info.connect_server)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn handle_loop(
|
||||
udp: UdpSocket,
|
||||
tun_reader: TunReader,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + 1500])?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::Ipv4Turn);
|
||||
net_packet.set_transport_protocol(ipv4::protocol::Protocol::Ipv4.into());
|
||||
net_packet.set_ttl(255);
|
||||
loop {
|
||||
let mut data = tun_reader.next()?;
|
||||
match handle(&udp, data.bytes_mut(), &cur_info, &mut net_packet) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(unix))]
|
||||
pub fn handle_loop(
|
||||
udp: UdpSocket,
|
||||
mut tun_reader: TunReader,
|
||||
cur_info: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + 1500])?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::Ipv4Turn);
|
||||
net_packet.set_transport_protocol(0);
|
||||
net_packet.set_ttl(255);
|
||||
let mut buf = [0u8; 1500];
|
||||
loop {
|
||||
let data = tun_reader.read(&mut buf)?;
|
||||
match handle(&udp, data, &cur_info, &mut net_packet) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
use std::net::{Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use chrono::Local;
|
||||
use crossbeam::channel::{Receiver, Sender, TrySendError};
|
||||
use packet::icmp::{icmp, Kind};
|
||||
use packet::ip::ipv4;
|
||||
use packet::ip::ipv4::packet::IpV4Packet;
|
||||
use protobuf::Message;
|
||||
|
||||
use crate::CurrentDeviceInfo;
|
||||
use crate::error::*;
|
||||
use crate::handle::{ADDR_TABLE, DEVICE_LIST, DIRECT_ROUTE_TABLE, NAT_INFO, Route, SERVER_RT};
|
||||
use crate::handle::punch_handler::PunchSender;
|
||||
use crate::handle::registration_handler::fast_registration;
|
||||
use crate::proto::message::{DeviceList, Punch, RegistrationResponse};
|
||||
use crate::protocol::{control_packet, NetPacket, Protocol, service_packet, turn_packet, Version};
|
||||
use crate::protocol::control_packet::{ControlPacket, PunchResponsePacket};
|
||||
use crate::protocol::error_packet::InErrorPacket;
|
||||
use crate::protocol::turn_packet::TurnPacket;
|
||||
use crate::tun_device::TunWriter;
|
||||
|
||||
pub fn recv_loop(
|
||||
udp: UdpSocket,
|
||||
server_addr: SocketAddr,
|
||||
other_sender: Sender<(SocketAddr, Vec<u8>)>,
|
||||
mut tun_writer: TunWriter,
|
||||
current_device: CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let mut buf = [0u8; 65536];
|
||||
let mut local_addr = udp.local_addr()?;
|
||||
local_addr.set_ip(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
loop {
|
||||
match udp.recv_from(&mut buf) {
|
||||
Ok((len, addr)) => {
|
||||
if addr == local_addr {
|
||||
//本地的包直接再发到网卡,这个主要用于处理当前虚拟ip的icmp ping
|
||||
if let Ok(ip) = IpV4Packet::new(&buf[..len]) {
|
||||
if ip.destination_ip() == current_device.virtual_ip {
|
||||
let _ = tun_writer.write(&buf[..len]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match recv_handle(
|
||||
&udp,
|
||||
addr,
|
||||
&mut buf[..len],
|
||||
&server_addr,
|
||||
&other_sender,
|
||||
&mut tun_writer,
|
||||
¤t_device,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(Error::Stop(str)) => {
|
||||
return Err(Error::Stop(str));
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_handle(
|
||||
udp: &UdpSocket,
|
||||
recv_addr: SocketAddr,
|
||||
buf: &mut [u8],
|
||||
_server_addr: &SocketAddr,
|
||||
other_sender: &Sender<(SocketAddr, Vec<u8>)>,
|
||||
tun_writer: &mut TunWriter,
|
||||
current_device: &CurrentDeviceInfo,
|
||||
) -> Result<()> {
|
||||
let mut net_packet = NetPacket::new(buf)?;
|
||||
match net_packet.protocol() {
|
||||
Protocol::Ipv4Turn => {
|
||||
let mut ipv4_turn_packet = TurnPacket::new(net_packet.payload_mut())?;
|
||||
let source = ipv4_turn_packet.source();
|
||||
let destination = ipv4_turn_packet.destination();
|
||||
let mut ipv4 = IpV4Packet::new(ipv4_turn_packet.payload_mut())?;
|
||||
if ipv4.source_ip() == source
|
||||
&& ipv4.destination_ip() == destination
|
||||
&& current_device.virtual_ip == ipv4.destination_ip()
|
||||
{
|
||||
if ipv4.protocol() == ipv4::protocol::Protocol::Icmp {
|
||||
let mut icmp_packet = icmp::IcmpPacket::new(ipv4.payload_mut())?;
|
||||
if icmp_packet.kind() == Kind::EchoRequest {
|
||||
//开启ping
|
||||
icmp_packet.set_kind(Kind::EchoReply);
|
||||
icmp_packet.update_checksum();
|
||||
ipv4.set_source_ip(destination);
|
||||
ipv4.set_destination_ip(source);
|
||||
ipv4.update_checksum();
|
||||
ipv4_turn_packet.set_source(destination);
|
||||
ipv4_turn_packet.set_destination(source);
|
||||
udp.send_to(net_packet.buffer(), recv_addr)?;
|
||||
} else {
|
||||
tun_writer.write(ipv4_turn_packet.payload())?;
|
||||
}
|
||||
} else {
|
||||
tun_writer.write(ipv4_turn_packet.payload())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::UnKnow(_) => {}
|
||||
_ => {
|
||||
//发送到子线程处理
|
||||
let v = net_packet.buffer().to_vec();
|
||||
match other_sender.try_send((recv_addr, v)) {
|
||||
Ok(_) => {}
|
||||
Err(TrySendError::Disconnected(_)) => {
|
||||
return Err(Error::Stop("处理线程停止".to_string()));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("子线程处理 :{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn other_loop(
|
||||
udp: UdpSocket,
|
||||
receiver: Receiver<(SocketAddr, Vec<u8>)>,
|
||||
current_device: CurrentDeviceInfo,
|
||||
sender: PunchSender,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
let (peer_addr, buf) = receiver.recv()?;
|
||||
match other_handle(&udp, buf, peer_addr, ¤t_device, &sender) {
|
||||
Ok(_) => {}
|
||||
Err(Error::Stop(str)) => {
|
||||
return Err(Error::Stop(str));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn other_handle(
|
||||
udp: &UdpSocket,
|
||||
buf: Vec<u8>,
|
||||
peer_addr: SocketAddr,
|
||||
current_device: &CurrentDeviceInfo,
|
||||
sender: &PunchSender,
|
||||
) -> Result<()> {
|
||||
let server_addr = current_device.connect_server;
|
||||
let mut net_packet = NetPacket::new(buf)?;
|
||||
match net_packet.protocol() {
|
||||
Protocol::Service => {
|
||||
if peer_addr != current_device.connect_server {
|
||||
return Ok(());
|
||||
}
|
||||
match service_packet::Protocol::from(net_packet.transport_protocol()) {
|
||||
service_packet::Protocol::RegistrationRequest => {}
|
||||
service_packet::Protocol::RegistrationResponse => {
|
||||
let response = RegistrationResponse::parse_from_bytes(net_packet.payload())?;
|
||||
crate::handle::init_nat_info(response.public_ip, response.public_port as u16);
|
||||
//todo 重连之后ip可能会发生改变(目前2分钟内未重连则会释放ip),需要更新本地ip(或者保证重连ip不变)
|
||||
}
|
||||
service_packet::Protocol::UpdateDeviceList => {
|
||||
let device_list = DeviceList::parse_from_bytes(net_packet.payload())?;
|
||||
let ip_list: Vec<Ipv4Addr> = device_list
|
||||
.virtual_ip_list
|
||||
.iter()
|
||||
.map(|ip| Ipv4Addr::from(*ip))
|
||||
.collect();
|
||||
let mut dev = DEVICE_LIST.lock();
|
||||
if dev.0 < device_list.epoch || device_list.epoch - dev.0 > u32::MAX >> 2 {
|
||||
dev.0 = device_list.epoch;
|
||||
dev.1 = ip_list;
|
||||
}
|
||||
}
|
||||
service_packet::Protocol::UnKnow(_) => {}
|
||||
}
|
||||
}
|
||||
Protocol::Error => {
|
||||
match InErrorPacket::new(net_packet.transport_protocol(), net_packet.payload())? {
|
||||
InErrorPacket::TokenError => {
|
||||
if server_addr == peer_addr {
|
||||
//停止整个应用
|
||||
return Err(Error::Stop("token无效".to_string()));
|
||||
}
|
||||
}
|
||||
InErrorPacket::Disconnect => {
|
||||
if server_addr == peer_addr {
|
||||
fast_registration(&udp, server_addr)?;
|
||||
}
|
||||
}
|
||||
InErrorPacket::OtherError(e) => {
|
||||
println!("{:?}", e.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Control => {
|
||||
match ControlPacket::new(net_packet.transport_protocol(), net_packet.payload())? {
|
||||
ControlPacket::PingPacket(ping) => {
|
||||
net_packet.set_transport_protocol(control_packet::Protocol::Pong.into());
|
||||
udp.send_to(&net_packet.buffer()[..12], peer_addr)?;
|
||||
}
|
||||
ControlPacket::PongPacket(pong_packet) => {
|
||||
let current_time = Local::now().timestamp_millis();
|
||||
let rt = current_time - pong_packet.time();
|
||||
if rt >= 0 {
|
||||
if peer_addr == server_addr {
|
||||
SERVER_RT.store(rt, Ordering::Relaxed)
|
||||
} else {
|
||||
//其他设备
|
||||
if let Some(virtual_ip) = ADDR_TABLE.get(&peer_addr) {
|
||||
if let Some(mut info) = DIRECT_ROUTE_TABLE.get_mut(&virtual_ip) {
|
||||
info.delay = rt;
|
||||
info.recv_time = current_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlPacket::PunchRequest(punch_request) => {
|
||||
// println!("打洞请求:{:?}", punch_request);
|
||||
let src = punch_request.source();
|
||||
drop(punch_request);
|
||||
//回应
|
||||
let mut punch_response = PunchResponsePacket::new(net_packet.payload_mut())?;
|
||||
punch_response.set_source(current_device.virtual_ip);
|
||||
net_packet.set_transport_protocol(control_packet::Protocol::PunchResponse.into());
|
||||
udp.send_to(net_packet.buffer(), peer_addr)?;
|
||||
let route = Route::new(peer_addr);
|
||||
DIRECT_ROUTE_TABLE.insert(src, route);
|
||||
ADDR_TABLE.insert(peer_addr, src);
|
||||
}
|
||||
ControlPacket::PunchResponse(punch_response) => {
|
||||
// println!("打洞响应:{:?}", punch_response);
|
||||
let route = Route::new(peer_addr);
|
||||
DIRECT_ROUTE_TABLE.insert(punch_response.source(), route);
|
||||
ADDR_TABLE.insert(peer_addr, punch_response.source());
|
||||
}
|
||||
}
|
||||
}
|
||||
Protocol::Ipv4Turn => {}
|
||||
Protocol::OtherTurn => {
|
||||
let turn_packet = TurnPacket::new(net_packet.payload())?;
|
||||
// println!("{:?}",turn_packet);
|
||||
let src = turn_packet.source();
|
||||
let dest = turn_packet.destination();
|
||||
if dest == current_device.virtual_ip {
|
||||
match turn_packet::Protocol::from(net_packet.transport_protocol()) {
|
||||
turn_packet::Protocol::Punch => {
|
||||
let punch = Punch::parse_from_bytes(turn_packet.payload())?;
|
||||
if punch.virtual_ip.to_be_bytes() == src.octets() {
|
||||
if !punch.reply {
|
||||
let mut punch_reply = Punch::new();
|
||||
punch_reply.reply = true;
|
||||
punch_reply.virtual_ip = u32::from_be_bytes(current_device.virtual_ip.octets());
|
||||
punch_reply.step = punch.step;
|
||||
if let Err(_) = sender.try_send(punch) {
|
||||
return Ok(());
|
||||
}
|
||||
let nat_info = NAT_INFO.lock();
|
||||
if let Some(info) = nat_info.as_ref() {
|
||||
punch_reply.public_ip_list = info.public_ips.clone();
|
||||
punch_reply.public_port = info.public_port as u32;
|
||||
punch_reply.public_port_range = info.public_port_range as u32;
|
||||
punch_reply.nat_type = protobuf::EnumOrUnknown::new(info.nat_type);
|
||||
drop(nat_info);
|
||||
let bytes = punch_reply.write_to_bytes()?;
|
||||
let mut net_packet = NetPacket::new(vec![0u8; 4 + 8 + bytes.len()])?;
|
||||
net_packet.set_version(Version::V1);
|
||||
net_packet.set_protocol(Protocol::OtherTurn);
|
||||
net_packet.set_transport_protocol(turn_packet::Protocol::Punch.into());
|
||||
net_packet.set_ttl(255);
|
||||
let mut turn_packet = TurnPacket::new(net_packet.payload_mut())?;
|
||||
turn_packet.set_source(current_device.virtual_ip);
|
||||
turn_packet.set_destination(src);
|
||||
turn_packet.set_payload(&bytes);
|
||||
udp.send_to(net_packet.buffer(), peer_addr)?;
|
||||
}
|
||||
} else {
|
||||
let _ = sender.try_send(punch);
|
||||
}
|
||||
}
|
||||
}
|
||||
turn_packet::Protocol::UnKnow(_) => {}
|
||||
}
|
||||
} else {
|
||||
panic!("ip")
|
||||
}
|
||||
}
|
||||
Protocol::UnKnow(p) => {
|
||||
println!("未知协议:{}", p)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod log;
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
pub fn log_init(log_name: &str) {
|
||||
log_init0(log_name, None);
|
||||
}
|
||||
pub fn log_init0(log_name: &str, yaml: Option<&str>) {
|
||||
let path = PathBuf::from("logs");
|
||||
|
||||
if !path.exists() {
|
||||
let _ = std::fs::create_dir(&path);
|
||||
}
|
||||
|
||||
let log_config = path.join(yaml.unwrap_or("log4rs.yaml"));
|
||||
|
||||
if !log_config.exists()
|
||||
&& let Ok(mut f) = std::fs::File::create(&log_config)
|
||||
{
|
||||
let log_file = path.join(format!("{log_name}.log"));
|
||||
let mut string = String::new();
|
||||
string.push_str(log_name);
|
||||
string.push_str(".{}.log");
|
||||
let log_file_pattern = path.join(&string);
|
||||
|
||||
let c = format!(
|
||||
r#"
|
||||
refresh_rate: 30 seconds
|
||||
appenders:
|
||||
rolling_file:
|
||||
kind: rolling_file
|
||||
path: {}
|
||||
append: true
|
||||
encoder:
|
||||
pattern: "{{d}} [{{f}}:{{L}}] {{h({{l}})}} {{M}}:{{m}}{{n}}"
|
||||
policy:
|
||||
kind: compound
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10 mb
|
||||
roller:
|
||||
kind: fixed_window
|
||||
pattern: {}
|
||||
base: 1
|
||||
count: 5
|
||||
console:
|
||||
kind: console
|
||||
encoder:
|
||||
pattern: "{{d}} {{h({{l}})}} {{m}}{{n}}"
|
||||
root:
|
||||
level: info
|
||||
appenders:
|
||||
- rolling_file
|
||||
- console
|
||||
"#,
|
||||
log_file.display(),
|
||||
log_file_pattern.display()
|
||||
);
|
||||
let _ = f.write_all(c.as_bytes());
|
||||
}
|
||||
|
||||
let _ = log4rs::init_file(log_config, Default::default());
|
||||
}
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
use std::{io, thread};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
|
||||
use crate::handle::{CurrentDeviceInfo, DEVICE_LIST, DIRECT_ROUTE_TABLE, NAT_INFO, NatInfo, SERVER_RT};
|
||||
use crate::handle::registration_handler::registration;
|
||||
use crate::tun_device::create_tun;
|
||||
|
||||
pub mod tun_device;
|
||||
pub mod nat;
|
||||
pub mod error;
|
||||
pub mod handle;
|
||||
pub mod proto;
|
||||
pub mod protocol;
|
||||
#[cfg(windows)]
|
||||
pub mod admin_check;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author = "Lu Beilin", version, about = "一个虚拟网络工具,启动后会获取一个ip,相同token下的设备之间可以用ip直接通信")]
|
||||
struct Args {
|
||||
/// 32位字符
|
||||
/// 相同token的设备之间才能通信。
|
||||
/// 建议使用uuid保证唯一性。
|
||||
/// 32-bit characters.
|
||||
/// Only devices with the same token can communicate with each other.
|
||||
/// It is recommended to use uuid to ensure uniqueness
|
||||
#[arg(short, long)]
|
||||
token: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
#[cfg(windows)]
|
||||
if !admin_check::is_app_elevated() {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
println!("{}", style("正在启动管理员权限执行...").red());
|
||||
if let Some(absolute_path) = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.to_str().map(|p| p.to_string()))
|
||||
{
|
||||
let _ = runas::Command::new(&absolute_path).args(&args[1..]).status()
|
||||
.expect("failed to execute");
|
||||
} else {
|
||||
panic!("failed to execute")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(any(unix))]
|
||||
if sudo::RunningAs::Root != sudo::check() {
|
||||
println!("{}", style("需要使用root权限执行...").red());
|
||||
sudo::escalate_if_needed().unwrap();
|
||||
}
|
||||
|
||||
println!("{}", style("启动服务...").green());
|
||||
|
||||
let token = args.token;
|
||||
// let d = Local::now().timestamp().to_string();
|
||||
let mac_address = mac_address::get_mac_address().unwrap().unwrap().to_string();
|
||||
let server_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(43, 139, 56, 10)), 29876);
|
||||
// let server_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 29876);
|
||||
let mut port = 101 as u16;
|
||||
let udp = loop {
|
||||
match UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(0), port))) {
|
||||
Ok(udp) => {
|
||||
break udp;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::AddrInUse {
|
||||
port += 1;
|
||||
} else {
|
||||
println!("创建udp失败:{:?}", e);
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
//注册
|
||||
let response = registration(&udp, server_address, token, mac_address).unwrap();
|
||||
{
|
||||
let ip_list = response
|
||||
.virtual_ip_list
|
||||
.iter()
|
||||
.map(|ip| Ipv4Addr::from(*ip))
|
||||
.collect();
|
||||
let mut dev = DEVICE_LIST.lock();
|
||||
dev.0 = response.epoch;
|
||||
dev.1 = ip_list;
|
||||
}
|
||||
let virtual_ip = Ipv4Addr::from(response.virtual_ip);
|
||||
let virtual_gateway = Ipv4Addr::from(response.virtual_gateway);
|
||||
let virtual_netmask = Ipv4Addr::from(response.virtual_netmask);
|
||||
println!("virtual_gateway:{:?}", virtual_gateway);
|
||||
println!("virtual_netmask:{:?}", virtual_netmask);
|
||||
println!("当前设备ip(virtual_ip):{}", style(virtual_ip).green());
|
||||
//心跳线程
|
||||
{
|
||||
let udp = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
if let Err(e) = handle::heartbeat_handler::handle_loop(udp, server_address) {
|
||||
println!("心跳线程停止:{:?}", e);
|
||||
}
|
||||
std::process::exit(1);
|
||||
});
|
||||
}
|
||||
//初始化nat数据
|
||||
handle::init_nat_info(response.public_ip, response.public_port as u16);
|
||||
// tun服务
|
||||
let (tun_writer, tun_reader) =
|
||||
create_tun(virtual_ip, virtual_netmask, virtual_gateway).unwrap();
|
||||
// 打洞数据通道
|
||||
let (punch_sender, cone_receiver, req_symmetric_receiver, res_symmetric_receiver) = handle::punch_handler::bounded();
|
||||
//udp数据处理
|
||||
{
|
||||
// 低优先级的udp数据通道
|
||||
let (sender, receiver) = crossbeam::channel::bounded(100);
|
||||
let udp1 = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::udp_recv_handler::recv_loop(
|
||||
udp1,
|
||||
server_address,
|
||||
sender,
|
||||
tun_writer,
|
||||
current_device,
|
||||
) {
|
||||
println!("udp数据处理线程停止:{:?}", e);
|
||||
}
|
||||
std::process::exit(1);
|
||||
});
|
||||
let udp1 = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::udp_recv_handler::other_loop(udp1, receiver, current_device, punch_sender) {
|
||||
println!("udp数据处理线程停止:{:?}", e);
|
||||
}
|
||||
std::process::exit(1);
|
||||
});
|
||||
}
|
||||
//打洞处理
|
||||
{
|
||||
let udp1 = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::punch_handler::cone_handle_loop(cone_receiver, udp1, current_device) {
|
||||
println!("打洞响应线程停止:{:?}", e);
|
||||
}
|
||||
});
|
||||
let udp1 = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::punch_handler::req_symmetric_handle_loop(req_symmetric_receiver, udp1, current_device) {
|
||||
println!("打洞触发线程停止:{:?}", e);
|
||||
}
|
||||
});
|
||||
let udp1 = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::punch_handler::res_symmetric_handle_loop(res_symmetric_receiver, udp1, current_device) {
|
||||
println!("打洞触发线程停止:{:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
//tun数据处理
|
||||
{
|
||||
let udp = udp.try_clone().unwrap();
|
||||
let _ = thread::spawn(move || {
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
if let Err(e) = handle::tun_handler::handle_loop(udp, tun_reader, current_device) {
|
||||
println!("tun数据处理线程停止:{:?}", e);
|
||||
}
|
||||
std::process::exit(1);
|
||||
});
|
||||
}
|
||||
use console::Term;
|
||||
let term = Term::stdout();
|
||||
let current_device = CurrentDeviceInfo::new(virtual_ip, virtual_gateway, virtual_netmask, server_address);
|
||||
loop {
|
||||
println!("{}", style("Please enter the command (Usage: list,status,exit,help):").color256(102));
|
||||
match term.read_line() {
|
||||
Ok(cmd) => {
|
||||
command(cmd.trim(), ¤t_device);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("read_line:{:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn command(cmd: &str, current_device: &CurrentDeviceInfo) {
|
||||
match cmd {
|
||||
"list" => {
|
||||
let server_delay = SERVER_RT.load(Ordering::Relaxed);
|
||||
let device_list_lock = DEVICE_LIST.lock();
|
||||
let (_epoch, device_list) = device_list_lock.clone();
|
||||
drop(device_list_lock);
|
||||
if device_list.is_empty() {
|
||||
println!("No other devices found");
|
||||
return;
|
||||
}
|
||||
for ip in device_list {
|
||||
if let Some(route_ref) = DIRECT_ROUTE_TABLE.get(&ip) {
|
||||
let str = if route_ref.value().delay >= 0 {
|
||||
format!("{}(p2p delay:{}ms)", ip, route_ref.value().delay)
|
||||
} else {
|
||||
format!("{}(p2p)", ip)
|
||||
};
|
||||
drop(route_ref);
|
||||
println!("{}", style(str).green());
|
||||
} else {
|
||||
let str = if server_delay >= 0 {
|
||||
format!("{}(relay delay:{}ms)", ip, server_delay * 2)
|
||||
} else {
|
||||
format!("{}(relay)", ip)
|
||||
};
|
||||
println!("{}", style(str).blue());
|
||||
}
|
||||
}
|
||||
}
|
||||
"status" => {
|
||||
let server_delay = SERVER_RT.load(Ordering::Relaxed);
|
||||
println!("Virtual ip:{}", style(current_device.virtual_ip).green());
|
||||
println!("Virtual gateway:{}", style(current_device.virtual_gateway).green());
|
||||
println!("Relay server :{}", style(current_device.connect_server).green());
|
||||
if server_delay >= 0 {
|
||||
println!("Delay of relay server :{}", style(server_delay).green());
|
||||
}
|
||||
}
|
||||
"help" | "h" => {
|
||||
println!("Options: ");
|
||||
println!("{} , Query the virtual IP of other devices", style("list").green());
|
||||
println!("{} , View current device status", style("status").green());
|
||||
println!("{} , Exit the program", style("exit").green());
|
||||
}
|
||||
"exit" => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
_ => {
|
||||
println!("command {} not fount. ", style(cmd).red());
|
||||
println!("Try to enter: '{}'", style("help").green());
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
use anyhow::Context;
|
||||
use args_config::{build_config_from_args_and_file, Args, FileConfig};
|
||||
use route_manager::Route;
|
||||
use std::path::Path;
|
||||
use vnt_ipc as vnt_core;
|
||||
|
||||
use vnt_core::core::NetworkManager;
|
||||
use vnt_core::utils::task_control::TaskGroupManager;
|
||||
pub mod args_config;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod extract_wintun_dll;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
if let Err(e) = main0().await {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
async fn main0() -> anyhow::Result<()> {
|
||||
let args = Args::parse_compatible();
|
||||
|
||||
vnt2::log::log_init("vnt2");
|
||||
log::info!("version: {:?}", env!("CARGO_PKG_VERSION"));
|
||||
#[cfg(windows)]
|
||||
extract_wintun_dll::extract_wintun();
|
||||
if args.conf_example {
|
||||
FileConfig::print_example(Some(Path::new("example_config.toml")))?;
|
||||
return Ok(());
|
||||
}
|
||||
let file_cfg = if let Some(path) = args.conf.as_ref() {
|
||||
log::info!("loaded config from {:?}", path);
|
||||
Some(FileConfig::load(path).context("failed to load config")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (config, ctrl_config) =
|
||||
build_config_from_args_and_file(Some(args), file_cfg).context("invalid configuration")?;
|
||||
|
||||
log::info!(
|
||||
"server: {}",
|
||||
config
|
||||
.server_addr
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
log::info!("network code: {}", config.network_code);
|
||||
log::info!("device id: {}", config.device_id);
|
||||
log::info!("device name: {}", config.device_name);
|
||||
log::info!("cert mode: {}", config.cert_mode);
|
||||
log::info!("compress: {}", config.compress);
|
||||
log::info!("rtx(quic channel): {}", config.rtx);
|
||||
let sub_input = config.input.clone();
|
||||
if !config.input.is_empty() {
|
||||
let x = config
|
||||
.input
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
log::info!("Sub network input:{x}");
|
||||
}
|
||||
if !config.output.is_empty() {
|
||||
let x = config
|
||||
.output
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
log::info!("Sub network output:{x}");
|
||||
}
|
||||
|
||||
if let Some(password_sign) = config.key_sign() {
|
||||
log::info!("password sign: {:?}", password_sign);
|
||||
}
|
||||
|
||||
let group_manager = TaskGroupManager::new();
|
||||
let (task_group, task_group_guard) =
|
||||
group_manager.create_task().context("create task group")?;
|
||||
|
||||
let mut network_manager = NetworkManager::create_network(Box::new(config), task_group)
|
||||
.await
|
||||
.context("create network")?;
|
||||
let reg_msg = network_manager.register().await.context("register")?;
|
||||
|
||||
if !network_manager.is_no_tun() {
|
||||
log::info!("启动网络:{}/{}", reg_msg.ip, reg_msg.prefix_len);
|
||||
network_manager.start_tun().await.context("start tun")?;
|
||||
network_manager
|
||||
.set_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
||||
.await
|
||||
.context("set network ip")?;
|
||||
if !sub_input.is_empty() {
|
||||
let if_index = network_manager
|
||||
.tun_if_index()
|
||||
.await
|
||||
.context("tun_if_index")?;
|
||||
let mut route_manager = route_manager::RouteManager::new()?;
|
||||
for x in sub_input {
|
||||
let route = Route::new(x.net.network().into(), x.net.prefix_len())
|
||||
.with_gateway(x.target_ip.into())
|
||||
.with_if_index(if_index);
|
||||
if let Err(e) = route_manager.add(&route) {
|
||||
log::error!("add route [{route}] error: {e:?}");
|
||||
} else {
|
||||
log::info!("add route [{route}] successful");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
"启动网络:{}/{} (无虚拟网卡)",
|
||||
reg_msg.ip,
|
||||
reg_msg.prefix_len
|
||||
);
|
||||
}
|
||||
let vnt_api = network_manager.vnt_api();
|
||||
if ctrl_config.ctrl_port.is_none_or(|p| p != 0) {
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = vnt_ipc::server::run_server(ctrl_config.ctrl_port, vnt_api).await {
|
||||
log::error!("ipc:{e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = network_manager.wait_all_stopped() => {
|
||||
break;
|
||||
}
|
||||
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
log::info!("Ctrl+c received!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(task_group_guard);
|
||||
log::info!("stop network");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use vnt_ipc::message::ipc_request::IpcCmd;
|
||||
|
||||
/// 操作vnt进程
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
/// 核心进程控制端口
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// 输出当前程序信息
|
||||
Info,
|
||||
/// 输出客户端ip列表
|
||||
Ips,
|
||||
/// 输出客户端信息列表
|
||||
#[command(alias = "list")]
|
||||
Clients,
|
||||
/// 输出IP路由信息
|
||||
Route,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let args = Args::parse();
|
||||
let port = args.port;
|
||||
let cmd = match args.command {
|
||||
Commands::Info => IpcCmd::AppInfo(Default::default()),
|
||||
|
||||
Commands::Ips => IpcCmd::ClientIps(Default::default()),
|
||||
Commands::Clients => IpcCmd::ClientList(Default::default()),
|
||||
Commands::Route => IpcCmd::AllRoute(Default::default()),
|
||||
};
|
||||
if let Err(e) = vnt_ipc::client::run_client(cmd, port).await {
|
||||
eprintln!("{:?}", e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod extract_wintun_dll;
|
||||
|
||||
/// vnt web服务
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// 本地http服务监听地址
|
||||
#[clap(long)]
|
||||
addr: Option<SocketAddr>,
|
||||
/// 加载vnt配置路径,配置内容参考web端的配置格式
|
||||
#[clap(long)]
|
||||
conf: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
if let Err(e) = main0().await {
|
||||
log::error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
async fn main0() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
vnt2::log::log_init("vnt2");
|
||||
log::info!("version: {:?}", env!("CARGO_PKG_VERSION"));
|
||||
#[cfg(windows)]
|
||||
extract_wintun_dll::extract_wintun();
|
||||
let addr = args.addr.unwrap_or("127.0.0.1:19099".parse()?);
|
||||
vnt_web::run_http_server(addr, args.conf).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use std::{io, thread};
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::proto::message::NatType;
|
||||
|
||||
// #[derive(Debug, Copy, Clone, PartialEq)]
|
||||
// pub enum NatType {
|
||||
// Symmetric,
|
||||
// Cone,
|
||||
// }
|
||||
//
|
||||
// impl Into<u8> for NatType {
|
||||
// fn into(self) -> u8 {
|
||||
// match self {
|
||||
// NatType::Symmetric => 0,
|
||||
// NatType::Cone => 1,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 返回所有公网ip和端口变化范围
|
||||
pub fn public_ip_list() -> io::Result<(NatType, Vec<Ipv4Addr>, u16)> {
|
||||
let mut hash_set = HashSet::new();
|
||||
let mut max_port_range = 0;
|
||||
let mut nat_type = NatType::Cone;
|
||||
let mut port = 88;
|
||||
for _ in 0..3 {
|
||||
let udp = loop {
|
||||
match UdpSocket::bind(SocketAddr::new(IpAddr::from(Ipv4Addr::from(0)), port)) {
|
||||
Ok(udp) => {
|
||||
break udp;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::AddrInUse {
|
||||
port += 1;
|
||||
continue;
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
let (set, min_port, max_port) = public_ip_list_(&udp)?;
|
||||
drop(udp);
|
||||
let port_range = max_port - min_port;
|
||||
//有多个ip或者端口有变化,说明是对称nat
|
||||
if nat_type == NatType::Cone && (set.len() > 1 || port_range != 0) {
|
||||
nat_type = NatType::Symmetric;
|
||||
}
|
||||
if max_port_range < port_range {
|
||||
max_port_range = port_range;
|
||||
}
|
||||
for x in set {
|
||||
hash_set.insert(x);
|
||||
}
|
||||
thread::sleep(Duration::from_micros(5));
|
||||
}
|
||||
Ok((nat_type, hash_set.into_iter().collect(), max_port_range))
|
||||
}
|
||||
|
||||
/// 测试样本较少,可能不对
|
||||
///
|
||||
/// - 移动宽带:锥形网络、一个ip、端口和局域网端口不相同
|
||||
/// - 电信宽带:锥形网络、一个ip,端口和局域网端口不相同
|
||||
/// - 联调宽带:对称网络、端口不变ip轮流用
|
||||
/// - 移动4g:对称网络、ip端口都变 使用小的端口变化量小
|
||||
/// - 联通4g:对称网络、只有一个ip 端口变化大
|
||||
/// - 电信4g:对称网络只有一个ip 公网端口比较连续
|
||||
/// - 综上:客户端使用小端口,针对对称网络 尝试所有ip 公网端口+-变化量的范围
|
||||
/// - 打通概率 移动宽带=电信宽带>联调宽带>电信4g>移动4g>>联调4g
|
||||
pub fn public_ip_list_(udp: &UdpSocket) -> io::Result<(HashSet<Ipv4Addr>, u16, u16)> {
|
||||
// println!("local port {:?}", udp.local_addr().unwrap().port());
|
||||
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
|
||||
let mut buf = [0u8; 128];
|
||||
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35061")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35062")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35061")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35062")?;
|
||||
let mut hash_set = HashSet::new();
|
||||
let mut count = 0;
|
||||
let mut min_port = 65535;
|
||||
let mut max_port = 0;
|
||||
for _ in 0..4 {
|
||||
if let Ok(len) = udp.recv(&mut buf) {
|
||||
if len != 16 || &buf[..10] != &b"NatType213"[..] {
|
||||
continue;
|
||||
}
|
||||
let port = u16::from_be_bytes([buf[14], buf[15]]);
|
||||
if min_port > port {
|
||||
min_port = port;
|
||||
}
|
||||
if max_port < port {
|
||||
max_port = port;
|
||||
}
|
||||
let ip = Ipv4Addr::new(buf[10], buf[11], buf[12], buf[13]);
|
||||
// println!("pub {:?}:{}", ip, port);
|
||||
hash_set.insert(ip);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
if count <= 1 {
|
||||
return Err(io::Error::from(io::ErrorKind::TimedOut));
|
||||
}
|
||||
Ok((hash_set, min_port, max_port))
|
||||
}
|
||||
|
||||
/// 返回nat类型
|
||||
pub fn nat_test() -> io::Result<NatType> {
|
||||
for _ in 0..3 {
|
||||
if NatType::Symmetric == nat_test_()? {
|
||||
return Ok(NatType::Symmetric);
|
||||
}
|
||||
thread::sleep(Duration::from_micros(5));
|
||||
}
|
||||
Ok(NatType::Cone)
|
||||
}
|
||||
|
||||
pub fn nat_test_() -> io::Result<NatType> {
|
||||
let udp = UdpSocket::bind("0.0.0.0:0")?;
|
||||
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
|
||||
let mut buf = [0u8; 128];
|
||||
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35061")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35062")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35061")?;
|
||||
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35062")?;
|
||||
let mut tmp_ip_port: Option<[u8; 6]> = None;
|
||||
let mut count = 0;
|
||||
for _ in 0..4 {
|
||||
if let Ok(len) = udp.recv(&mut buf) {
|
||||
if len != 16 || &buf[..10] != &b"NatType213"[..] {
|
||||
continue;
|
||||
}
|
||||
count += 1;
|
||||
let mut ip_port = [0u8; 6];
|
||||
ip_port.copy_from_slice(&buf[10..16]);
|
||||
if let Some(tmp_ip_port) = &tmp_ip_port {
|
||||
if tmp_ip_port != &ip_port {
|
||||
return Ok(NatType::Symmetric);
|
||||
}
|
||||
} else {
|
||||
tmp_ip_port = Some(ip_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
if count <= 1 {
|
||||
return Err(io::Error::from(io::ErrorKind::TimedOut));
|
||||
}
|
||||
Ok(NatType::Cone)
|
||||
}
|
||||
#[test]
|
||||
fn nat_test_run(){
|
||||
let udp = UdpSocket::bind("0.0.0.0:101").unwrap();
|
||||
let print = public_ip_list_(&udp).unwrap();
|
||||
println!("{:?}",print);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod check;
|
||||
@@ -1,941 +0,0 @@
|
||||
// This file is generated by rust-protobuf 3.2.0. Do not edit
|
||||
// .proto file is parsed by pure
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_results)]
|
||||
#![allow(unused_mut)]
|
||||
|
||||
//! Generated file from `message.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0;
|
||||
|
||||
#[derive(PartialEq,Clone,Default,Debug)]
|
||||
// @@protoc_insertion_point(message:RegistrationRequest)
|
||||
pub struct RegistrationRequest {
|
||||
// message fields
|
||||
// @@protoc_insertion_point(field:RegistrationRequest.token)
|
||||
pub token: ::std::string::String,
|
||||
// @@protoc_insertion_point(field:RegistrationRequest.mac_address)
|
||||
pub mac_address: ::std::string::String,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:RegistrationRequest.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a RegistrationRequest {
|
||||
fn default() -> &'a RegistrationRequest {
|
||||
<RegistrationRequest as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistrationRequest {
|
||||
pub fn new() -> RegistrationRequest {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
let mut fields = ::std::vec::Vec::with_capacity(2);
|
||||
let mut oneofs = ::std::vec::Vec::with_capacity(0);
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"token",
|
||||
|m: &RegistrationRequest| { &m.token },
|
||||
|m: &mut RegistrationRequest| { &mut m.token },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"mac_address",
|
||||
|m: &RegistrationRequest| { &m.mac_address },
|
||||
|m: &mut RegistrationRequest| { &mut m.mac_address },
|
||||
));
|
||||
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RegistrationRequest>(
|
||||
"RegistrationRequest",
|
||||
fields,
|
||||
oneofs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for RegistrationRequest {
|
||||
const NAME: &'static str = "RegistrationRequest";
|
||||
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
|
||||
while let Some(tag) = is.read_raw_tag_or_eof()? {
|
||||
match tag {
|
||||
10 => {
|
||||
self.token = is.read_string()?;
|
||||
},
|
||||
18 => {
|
||||
self.mac_address = is.read_string()?;
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u64 {
|
||||
let mut my_size = 0;
|
||||
if !self.token.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.token);
|
||||
}
|
||||
if !self.mac_address.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(2, &self.mac_address);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
|
||||
self.special_fields.cached_size().set(my_size as u32);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
|
||||
if !self.token.is_empty() {
|
||||
os.write_string(1, &self.token)?;
|
||||
}
|
||||
if !self.mac_address.is_empty() {
|
||||
os.write_string(2, &self.mac_address)?;
|
||||
}
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn special_fields(&self) -> &::protobuf::SpecialFields {
|
||||
&self.special_fields
|
||||
}
|
||||
|
||||
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
|
||||
&mut self.special_fields
|
||||
}
|
||||
|
||||
fn new() -> RegistrationRequest {
|
||||
RegistrationRequest::new()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.token.clear();
|
||||
self.mac_address.clear();
|
||||
self.special_fields.clear();
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static RegistrationRequest {
|
||||
static instance: RegistrationRequest = RegistrationRequest {
|
||||
token: ::std::string::String::new(),
|
||||
mac_address: ::std::string::String::new(),
|
||||
special_fields: ::protobuf::SpecialFields::new(),
|
||||
};
|
||||
&instance
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::MessageFull for RegistrationRequest {
|
||||
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().message_by_package_relative_name("RegistrationRequest").unwrap()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for RegistrationRequest {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for RegistrationRequest {
|
||||
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default,Debug)]
|
||||
// @@protoc_insertion_point(message:RegistrationResponse)
|
||||
pub struct RegistrationResponse {
|
||||
// message fields
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.virtual_ip)
|
||||
pub virtual_ip: u32,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.virtual_gateway)
|
||||
pub virtual_gateway: u32,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.virtual_netmask)
|
||||
pub virtual_netmask: u32,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.epoch)
|
||||
pub epoch: u32,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.virtual_ip_list)
|
||||
pub virtual_ip_list: ::std::vec::Vec<u32>,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.public_ip)
|
||||
pub public_ip: u32,
|
||||
// @@protoc_insertion_point(field:RegistrationResponse.public_port)
|
||||
pub public_port: u32,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:RegistrationResponse.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a RegistrationResponse {
|
||||
fn default() -> &'a RegistrationResponse {
|
||||
<RegistrationResponse as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistrationResponse {
|
||||
pub fn new() -> RegistrationResponse {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
let mut fields = ::std::vec::Vec::with_capacity(7);
|
||||
let mut oneofs = ::std::vec::Vec::with_capacity(0);
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"virtual_ip",
|
||||
|m: &RegistrationResponse| { &m.virtual_ip },
|
||||
|m: &mut RegistrationResponse| { &mut m.virtual_ip },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"virtual_gateway",
|
||||
|m: &RegistrationResponse| { &m.virtual_gateway },
|
||||
|m: &mut RegistrationResponse| { &mut m.virtual_gateway },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"virtual_netmask",
|
||||
|m: &RegistrationResponse| { &m.virtual_netmask },
|
||||
|m: &mut RegistrationResponse| { &mut m.virtual_netmask },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"epoch",
|
||||
|m: &RegistrationResponse| { &m.epoch },
|
||||
|m: &mut RegistrationResponse| { &mut m.epoch },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
|
||||
"virtual_ip_list",
|
||||
|m: &RegistrationResponse| { &m.virtual_ip_list },
|
||||
|m: &mut RegistrationResponse| { &mut m.virtual_ip_list },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"public_ip",
|
||||
|m: &RegistrationResponse| { &m.public_ip },
|
||||
|m: &mut RegistrationResponse| { &mut m.public_ip },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"public_port",
|
||||
|m: &RegistrationResponse| { &m.public_port },
|
||||
|m: &mut RegistrationResponse| { &mut m.public_port },
|
||||
));
|
||||
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RegistrationResponse>(
|
||||
"RegistrationResponse",
|
||||
fields,
|
||||
oneofs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for RegistrationResponse {
|
||||
const NAME: &'static str = "RegistrationResponse";
|
||||
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
|
||||
while let Some(tag) = is.read_raw_tag_or_eof()? {
|
||||
match tag {
|
||||
13 => {
|
||||
self.virtual_ip = is.read_fixed32()?;
|
||||
},
|
||||
21 => {
|
||||
self.virtual_gateway = is.read_fixed32()?;
|
||||
},
|
||||
29 => {
|
||||
self.virtual_netmask = is.read_fixed32()?;
|
||||
},
|
||||
32 => {
|
||||
self.epoch = is.read_uint32()?;
|
||||
},
|
||||
42 => {
|
||||
is.read_repeated_packed_fixed32_into(&mut self.virtual_ip_list)?;
|
||||
},
|
||||
45 => {
|
||||
self.virtual_ip_list.push(is.read_fixed32()?);
|
||||
},
|
||||
53 => {
|
||||
self.public_ip = is.read_fixed32()?;
|
||||
},
|
||||
56 => {
|
||||
self.public_port = is.read_uint32()?;
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u64 {
|
||||
let mut my_size = 0;
|
||||
if self.virtual_ip != 0 {
|
||||
my_size += 1 + 4;
|
||||
}
|
||||
if self.virtual_gateway != 0 {
|
||||
my_size += 1 + 4;
|
||||
}
|
||||
if self.virtual_netmask != 0 {
|
||||
my_size += 1 + 4;
|
||||
}
|
||||
if self.epoch != 0 {
|
||||
my_size += ::protobuf::rt::uint32_size(4, self.epoch);
|
||||
}
|
||||
my_size += 5 * self.virtual_ip_list.len() as u64;
|
||||
if self.public_ip != 0 {
|
||||
my_size += 1 + 4;
|
||||
}
|
||||
if self.public_port != 0 {
|
||||
my_size += ::protobuf::rt::uint32_size(7, self.public_port);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
|
||||
self.special_fields.cached_size().set(my_size as u32);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
|
||||
if self.virtual_ip != 0 {
|
||||
os.write_fixed32(1, self.virtual_ip)?;
|
||||
}
|
||||
if self.virtual_gateway != 0 {
|
||||
os.write_fixed32(2, self.virtual_gateway)?;
|
||||
}
|
||||
if self.virtual_netmask != 0 {
|
||||
os.write_fixed32(3, self.virtual_netmask)?;
|
||||
}
|
||||
if self.epoch != 0 {
|
||||
os.write_uint32(4, self.epoch)?;
|
||||
}
|
||||
for v in &self.virtual_ip_list {
|
||||
os.write_fixed32(5, *v)?;
|
||||
};
|
||||
if self.public_ip != 0 {
|
||||
os.write_fixed32(6, self.public_ip)?;
|
||||
}
|
||||
if self.public_port != 0 {
|
||||
os.write_uint32(7, self.public_port)?;
|
||||
}
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn special_fields(&self) -> &::protobuf::SpecialFields {
|
||||
&self.special_fields
|
||||
}
|
||||
|
||||
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
|
||||
&mut self.special_fields
|
||||
}
|
||||
|
||||
fn new() -> RegistrationResponse {
|
||||
RegistrationResponse::new()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.virtual_ip = 0;
|
||||
self.virtual_gateway = 0;
|
||||
self.virtual_netmask = 0;
|
||||
self.epoch = 0;
|
||||
self.virtual_ip_list.clear();
|
||||
self.public_ip = 0;
|
||||
self.public_port = 0;
|
||||
self.special_fields.clear();
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static RegistrationResponse {
|
||||
static instance: RegistrationResponse = RegistrationResponse {
|
||||
virtual_ip: 0,
|
||||
virtual_gateway: 0,
|
||||
virtual_netmask: 0,
|
||||
epoch: 0,
|
||||
virtual_ip_list: ::std::vec::Vec::new(),
|
||||
public_ip: 0,
|
||||
public_port: 0,
|
||||
special_fields: ::protobuf::SpecialFields::new(),
|
||||
};
|
||||
&instance
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::MessageFull for RegistrationResponse {
|
||||
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().message_by_package_relative_name("RegistrationResponse").unwrap()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for RegistrationResponse {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for RegistrationResponse {
|
||||
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default,Debug)]
|
||||
// @@protoc_insertion_point(message:DeviceList)
|
||||
pub struct DeviceList {
|
||||
// message fields
|
||||
// @@protoc_insertion_point(field:DeviceList.epoch)
|
||||
pub epoch: u32,
|
||||
// @@protoc_insertion_point(field:DeviceList.virtual_ip_list)
|
||||
pub virtual_ip_list: ::std::vec::Vec<u32>,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:DeviceList.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a DeviceList {
|
||||
fn default() -> &'a DeviceList {
|
||||
<DeviceList as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceList {
|
||||
pub fn new() -> DeviceList {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
let mut fields = ::std::vec::Vec::with_capacity(2);
|
||||
let mut oneofs = ::std::vec::Vec::with_capacity(0);
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"epoch",
|
||||
|m: &DeviceList| { &m.epoch },
|
||||
|m: &mut DeviceList| { &mut m.epoch },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
|
||||
"virtual_ip_list",
|
||||
|m: &DeviceList| { &m.virtual_ip_list },
|
||||
|m: &mut DeviceList| { &mut m.virtual_ip_list },
|
||||
));
|
||||
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<DeviceList>(
|
||||
"DeviceList",
|
||||
fields,
|
||||
oneofs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for DeviceList {
|
||||
const NAME: &'static str = "DeviceList";
|
||||
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
|
||||
while let Some(tag) = is.read_raw_tag_or_eof()? {
|
||||
match tag {
|
||||
8 => {
|
||||
self.epoch = is.read_uint32()?;
|
||||
},
|
||||
18 => {
|
||||
is.read_repeated_packed_fixed32_into(&mut self.virtual_ip_list)?;
|
||||
},
|
||||
21 => {
|
||||
self.virtual_ip_list.push(is.read_fixed32()?);
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u64 {
|
||||
let mut my_size = 0;
|
||||
if self.epoch != 0 {
|
||||
my_size += ::protobuf::rt::uint32_size(1, self.epoch);
|
||||
}
|
||||
my_size += 5 * self.virtual_ip_list.len() as u64;
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
|
||||
self.special_fields.cached_size().set(my_size as u32);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
|
||||
if self.epoch != 0 {
|
||||
os.write_uint32(1, self.epoch)?;
|
||||
}
|
||||
for v in &self.virtual_ip_list {
|
||||
os.write_fixed32(2, *v)?;
|
||||
};
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn special_fields(&self) -> &::protobuf::SpecialFields {
|
||||
&self.special_fields
|
||||
}
|
||||
|
||||
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
|
||||
&mut self.special_fields
|
||||
}
|
||||
|
||||
fn new() -> DeviceList {
|
||||
DeviceList::new()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.epoch = 0;
|
||||
self.virtual_ip_list.clear();
|
||||
self.special_fields.clear();
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static DeviceList {
|
||||
static instance: DeviceList = DeviceList {
|
||||
epoch: 0,
|
||||
virtual_ip_list: ::std::vec::Vec::new(),
|
||||
special_fields: ::protobuf::SpecialFields::new(),
|
||||
};
|
||||
&instance
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::MessageFull for DeviceList {
|
||||
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().message_by_package_relative_name("DeviceList").unwrap()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for DeviceList {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for DeviceList {
|
||||
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default,Debug)]
|
||||
// @@protoc_insertion_point(message:Punch)
|
||||
pub struct Punch {
|
||||
// message fields
|
||||
// @@protoc_insertion_point(field:Punch.virtual_ip)
|
||||
pub virtual_ip: u32,
|
||||
// @@protoc_insertion_point(field:Punch.public_ip_list)
|
||||
pub public_ip_list: ::std::vec::Vec<u32>,
|
||||
// @@protoc_insertion_point(field:Punch.public_port)
|
||||
pub public_port: u32,
|
||||
// @@protoc_insertion_point(field:Punch.public_port_range)
|
||||
pub public_port_range: u32,
|
||||
// @@protoc_insertion_point(field:Punch.nat_type)
|
||||
pub nat_type: ::protobuf::EnumOrUnknown<NatType>,
|
||||
// @@protoc_insertion_point(field:Punch.reply)
|
||||
pub reply: bool,
|
||||
// @@protoc_insertion_point(field:Punch.step)
|
||||
pub step: ::protobuf::EnumOrUnknown<Step>,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:Punch.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a Punch {
|
||||
fn default() -> &'a Punch {
|
||||
<Punch as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl Punch {
|
||||
pub fn new() -> Punch {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
let mut fields = ::std::vec::Vec::with_capacity(7);
|
||||
let mut oneofs = ::std::vec::Vec::with_capacity(0);
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"virtual_ip",
|
||||
|m: &Punch| { &m.virtual_ip },
|
||||
|m: &mut Punch| { &mut m.virtual_ip },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
|
||||
"public_ip_list",
|
||||
|m: &Punch| { &m.public_ip_list },
|
||||
|m: &mut Punch| { &mut m.public_ip_list },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"public_port",
|
||||
|m: &Punch| { &m.public_port },
|
||||
|m: &mut Punch| { &mut m.public_port },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"public_port_range",
|
||||
|m: &Punch| { &m.public_port_range },
|
||||
|m: &mut Punch| { &mut m.public_port_range },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"nat_type",
|
||||
|m: &Punch| { &m.nat_type },
|
||||
|m: &mut Punch| { &mut m.nat_type },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"reply",
|
||||
|m: &Punch| { &m.reply },
|
||||
|m: &mut Punch| { &mut m.reply },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>(
|
||||
"step",
|
||||
|m: &Punch| { &m.step },
|
||||
|m: &mut Punch| { &mut m.step },
|
||||
));
|
||||
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<Punch>(
|
||||
"Punch",
|
||||
fields,
|
||||
oneofs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for Punch {
|
||||
const NAME: &'static str = "Punch";
|
||||
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
|
||||
while let Some(tag) = is.read_raw_tag_or_eof()? {
|
||||
match tag {
|
||||
13 => {
|
||||
self.virtual_ip = is.read_fixed32()?;
|
||||
},
|
||||
18 => {
|
||||
is.read_repeated_packed_fixed32_into(&mut self.public_ip_list)?;
|
||||
},
|
||||
21 => {
|
||||
self.public_ip_list.push(is.read_fixed32()?);
|
||||
},
|
||||
24 => {
|
||||
self.public_port = is.read_uint32()?;
|
||||
},
|
||||
32 => {
|
||||
self.public_port_range = is.read_uint32()?;
|
||||
},
|
||||
40 => {
|
||||
self.nat_type = is.read_enum_or_unknown()?;
|
||||
},
|
||||
48 => {
|
||||
self.reply = is.read_bool()?;
|
||||
},
|
||||
56 => {
|
||||
self.step = is.read_enum_or_unknown()?;
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u64 {
|
||||
let mut my_size = 0;
|
||||
if self.virtual_ip != 0 {
|
||||
my_size += 1 + 4;
|
||||
}
|
||||
my_size += 5 * self.public_ip_list.len() as u64;
|
||||
if self.public_port != 0 {
|
||||
my_size += ::protobuf::rt::uint32_size(3, self.public_port);
|
||||
}
|
||||
if self.public_port_range != 0 {
|
||||
my_size += ::protobuf::rt::uint32_size(4, self.public_port_range);
|
||||
}
|
||||
if self.nat_type != ::protobuf::EnumOrUnknown::new(NatType::Symmetric) {
|
||||
my_size += ::protobuf::rt::int32_size(5, self.nat_type.value());
|
||||
}
|
||||
if self.reply != false {
|
||||
my_size += 1 + 1;
|
||||
}
|
||||
if self.step != ::protobuf::EnumOrUnknown::new(Step::Step1) {
|
||||
my_size += ::protobuf::rt::int32_size(7, self.step.value());
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
|
||||
self.special_fields.cached_size().set(my_size as u32);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
|
||||
if self.virtual_ip != 0 {
|
||||
os.write_fixed32(1, self.virtual_ip)?;
|
||||
}
|
||||
for v in &self.public_ip_list {
|
||||
os.write_fixed32(2, *v)?;
|
||||
};
|
||||
if self.public_port != 0 {
|
||||
os.write_uint32(3, self.public_port)?;
|
||||
}
|
||||
if self.public_port_range != 0 {
|
||||
os.write_uint32(4, self.public_port_range)?;
|
||||
}
|
||||
if self.nat_type != ::protobuf::EnumOrUnknown::new(NatType::Symmetric) {
|
||||
os.write_enum(5, ::protobuf::EnumOrUnknown::value(&self.nat_type))?;
|
||||
}
|
||||
if self.reply != false {
|
||||
os.write_bool(6, self.reply)?;
|
||||
}
|
||||
if self.step != ::protobuf::EnumOrUnknown::new(Step::Step1) {
|
||||
os.write_enum(7, ::protobuf::EnumOrUnknown::value(&self.step))?;
|
||||
}
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn special_fields(&self) -> &::protobuf::SpecialFields {
|
||||
&self.special_fields
|
||||
}
|
||||
|
||||
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
|
||||
&mut self.special_fields
|
||||
}
|
||||
|
||||
fn new() -> Punch {
|
||||
Punch::new()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.virtual_ip = 0;
|
||||
self.public_ip_list.clear();
|
||||
self.public_port = 0;
|
||||
self.public_port_range = 0;
|
||||
self.nat_type = ::protobuf::EnumOrUnknown::new(NatType::Symmetric);
|
||||
self.reply = false;
|
||||
self.step = ::protobuf::EnumOrUnknown::new(Step::Step1);
|
||||
self.special_fields.clear();
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static Punch {
|
||||
static instance: Punch = Punch {
|
||||
virtual_ip: 0,
|
||||
public_ip_list: ::std::vec::Vec::new(),
|
||||
public_port: 0,
|
||||
public_port_range: 0,
|
||||
nat_type: ::protobuf::EnumOrUnknown::from_i32(0),
|
||||
reply: false,
|
||||
step: ::protobuf::EnumOrUnknown::from_i32(0),
|
||||
special_fields: ::protobuf::SpecialFields::new(),
|
||||
};
|
||||
&instance
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::MessageFull for Punch {
|
||||
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().message_by_package_relative_name("Punch").unwrap()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for Punch {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for Punch {
|
||||
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)]
|
||||
// @@protoc_insertion_point(enum:NatType)
|
||||
pub enum NatType {
|
||||
// @@protoc_insertion_point(enum_value:NatType.Symmetric)
|
||||
Symmetric = 0,
|
||||
// @@protoc_insertion_point(enum_value:NatType.Cone)
|
||||
Cone = 1,
|
||||
}
|
||||
|
||||
impl ::protobuf::Enum for NatType {
|
||||
const NAME: &'static str = "NatType";
|
||||
|
||||
fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
|
||||
fn from_i32(value: i32) -> ::std::option::Option<NatType> {
|
||||
match value {
|
||||
0 => ::std::option::Option::Some(NatType::Symmetric),
|
||||
1 => ::std::option::Option::Some(NatType::Cone),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
const VALUES: &'static [NatType] = &[
|
||||
NatType::Symmetric,
|
||||
NatType::Cone,
|
||||
];
|
||||
}
|
||||
|
||||
impl ::protobuf::EnumFull for NatType {
|
||||
fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().enum_by_package_relative_name("NatType").unwrap()).clone()
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor {
|
||||
let index = *self as usize;
|
||||
Self::enum_descriptor().value_by_index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::default::Default for NatType {
|
||||
fn default() -> Self {
|
||||
NatType::Symmetric
|
||||
}
|
||||
}
|
||||
|
||||
impl NatType {
|
||||
fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData {
|
||||
::protobuf::reflect::GeneratedEnumDescriptorData::new::<NatType>("NatType")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)]
|
||||
// @@protoc_insertion_point(enum:Step)
|
||||
pub enum Step {
|
||||
// @@protoc_insertion_point(enum_value:Step.Step1)
|
||||
Step1 = 0,
|
||||
// @@protoc_insertion_point(enum_value:Step.Step2)
|
||||
Step2 = 1,
|
||||
// @@protoc_insertion_point(enum_value:Step.Step3)
|
||||
Step3 = 2,
|
||||
// @@protoc_insertion_point(enum_value:Step.Step4)
|
||||
Step4 = 3,
|
||||
}
|
||||
|
||||
impl ::protobuf::Enum for Step {
|
||||
const NAME: &'static str = "Step";
|
||||
|
||||
fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
|
||||
fn from_i32(value: i32) -> ::std::option::Option<Step> {
|
||||
match value {
|
||||
0 => ::std::option::Option::Some(Step::Step1),
|
||||
1 => ::std::option::Option::Some(Step::Step2),
|
||||
2 => ::std::option::Option::Some(Step::Step3),
|
||||
3 => ::std::option::Option::Some(Step::Step4),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
const VALUES: &'static [Step] = &[
|
||||
Step::Step1,
|
||||
Step::Step2,
|
||||
Step::Step3,
|
||||
Step::Step4,
|
||||
];
|
||||
}
|
||||
|
||||
impl ::protobuf::EnumFull for Step {
|
||||
fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| file_descriptor().enum_by_package_relative_name("Step").unwrap()).clone()
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor {
|
||||
let index = *self as usize;
|
||||
Self::enum_descriptor().value_by_index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::default::Default for Step {
|
||||
fn default() -> Self {
|
||||
Step::Step1
|
||||
}
|
||||
}
|
||||
|
||||
impl Step {
|
||||
fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData {
|
||||
::protobuf::reflect::GeneratedEnumDescriptorData::new::<Step>("Step")
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\rmessage.proto\"L\n\x13RegistrationRequest\x12\x14\n\x05token\x18\x01\
|
||||
\x20\x01(\tR\x05token\x12\x1f\n\x0bmac_address\x18\x02\x20\x01(\tR\nmacA\
|
||||
ddress\"\x83\x02\n\x14RegistrationResponse\x12\x1d\n\nvirtual_ip\x18\x01\
|
||||
\x20\x01(\x07R\tvirtualIp\x12'\n\x0fvirtual_gateway\x18\x02\x20\x01(\x07\
|
||||
R\x0evirtualGateway\x12'\n\x0fvirtual_netmask\x18\x03\x20\x01(\x07R\x0ev\
|
||||
irtualNetmask\x12\x14\n\x05epoch\x18\x04\x20\x01(\rR\x05epoch\x12&\n\x0f\
|
||||
virtual_ip_list\x18\x05\x20\x03(\x07R\rvirtualIpList\x12\x1b\n\tpublic_i\
|
||||
p\x18\x06\x20\x01(\x07R\x08publicIp\x12\x1f\n\x0bpublic_port\x18\x07\x20\
|
||||
\x01(\rR\npublicPort\"J\n\nDeviceList\x12\x14\n\x05epoch\x18\x01\x20\x01\
|
||||
(\rR\x05epoch\x12&\n\x0fvirtual_ip_list\x18\x02\x20\x03(\x07R\rvirtualIp\
|
||||
List\"\xef\x01\n\x05Punch\x12\x1d\n\nvirtual_ip\x18\x01\x20\x01(\x07R\tv\
|
||||
irtualIp\x12$\n\x0epublic_ip_list\x18\x02\x20\x03(\x07R\x0cpublicIpList\
|
||||
\x12\x1f\n\x0bpublic_port\x18\x03\x20\x01(\rR\npublicPort\x12*\n\x11publ\
|
||||
ic_port_range\x18\x04\x20\x01(\rR\x0fpublicPortRange\x12#\n\x08nat_type\
|
||||
\x18\x05\x20\x01(\x0e2\x08.NatTypeR\x07natType\x12\x14\n\x05reply\x18\
|
||||
\x06\x20\x01(\x08R\x05reply\x12\x19\n\x04step\x18\x07\x20\x01(\x0e2\x05.\
|
||||
StepR\x04step*\"\n\x07NatType\x12\r\n\tSymmetric\x10\0\x12\x08\n\x04Cone\
|
||||
\x10\x01*2\n\x04Step\x12\t\n\x05Step1\x10\0\x12\t\n\x05Step2\x10\x01\x12\
|
||||
\t\n\x05Step3\x10\x02\x12\t\n\x05Step4\x10\x03b\x06proto3\
|
||||
";
|
||||
|
||||
/// `FileDescriptorProto` object which was a source for this generated file
|
||||
fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new();
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// `FileDescriptor` object which allows dynamic access to files
|
||||
pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
|
||||
static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
file_descriptor.get(|| {
|
||||
let generated_file_descriptor = generated_file_descriptor_lazy.get(|| {
|
||||
let mut deps = ::std::vec::Vec::with_capacity(0);
|
||||
let mut messages = ::std::vec::Vec::with_capacity(4);
|
||||
messages.push(RegistrationRequest::generated_message_descriptor_data());
|
||||
messages.push(RegistrationResponse::generated_message_descriptor_data());
|
||||
messages.push(DeviceList::generated_message_descriptor_data());
|
||||
messages.push(Punch::generated_message_descriptor_data());
|
||||
let mut enums = ::std::vec::Vec::with_capacity(2);
|
||||
enums.push(NatType::generated_enum_descriptor_data());
|
||||
enums.push(Step::generated_enum_descriptor_data());
|
||||
::protobuf::reflect::GeneratedFileDescriptor::new_generated(
|
||||
file_descriptor_proto(),
|
||||
deps,
|
||||
messages,
|
||||
enums,
|
||||
)
|
||||
});
|
||||
::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor)
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// @generated
|
||||
|
||||
pub mod message;
|
||||
@@ -1,223 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Protocol {
|
||||
Ping,
|
||||
Pong,
|
||||
PunchRequest,
|
||||
PunchResponse,
|
||||
UnKnow(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Protocol::Ping,
|
||||
2 => Protocol::Pong,
|
||||
3 => Protocol::PunchRequest,
|
||||
4 => Protocol::PunchResponse,
|
||||
val => Protocol::UnKnow(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Protocol::Ping => 1,
|
||||
Protocol::Pong => 2,
|
||||
Protocol::PunchRequest => 3,
|
||||
Protocol::PunchResponse => 4,
|
||||
Protocol::UnKnow(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ControlPacket<B> {
|
||||
PingPacket(PingPacket<B>),
|
||||
PongPacket(PongPacket<B>),
|
||||
PunchRequest(PunchRequestPacket<B>),
|
||||
PunchResponse(PunchResponsePacket<B>),
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> ControlPacket<B> {
|
||||
pub fn new(protocol: u8, buffer: B) -> Result<ControlPacket<B>> {
|
||||
match Protocol::from(protocol) {
|
||||
Protocol::Ping => Ok(ControlPacket::PingPacket(PingPacket::new(buffer)?)),
|
||||
Protocol::Pong => Ok(ControlPacket::PongPacket(PongPacket::new(buffer)?)),
|
||||
Protocol::PunchRequest => Ok(ControlPacket::PunchRequest(PunchRequestPacket::new(
|
||||
buffer,
|
||||
)?)),
|
||||
Protocol::PunchResponse => Ok(ControlPacket::PunchResponse(PunchResponsePacket::new(
|
||||
buffer,
|
||||
)?)),
|
||||
Protocol::UnKnow(_) => Err(Error::NotSupport),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 网络探针
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PingPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PongPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
|
||||
impl<B: AsRef<[u8]>> PingPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<PingPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
if len != 8 + 4 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(PingPacket { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> PingPacket<B> {
|
||||
pub fn time(&self) -> i64 {
|
||||
i64::from_be_bytes(self.buffer.as_ref()[..8].try_into().unwrap())
|
||||
}
|
||||
pub fn epoch(&self) -> u32 {
|
||||
u32::from_be_bytes(self.buffer.as_ref()[8..12].try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> PingPacket<B> {
|
||||
pub fn set_time(&mut self, time: i64) {
|
||||
self.buffer.as_mut()[..8].copy_from_slice(&time.to_be_bytes())
|
||||
}
|
||||
pub fn set_epoch(&mut self, epoch: u32) {
|
||||
self.buffer.as_mut()[8..12].copy_from_slice(&epoch.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for PingPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PingPacket")
|
||||
.field("time", &self.time())
|
||||
.field("epoch", &self.epoch())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> PongPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<PongPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
if len != 8 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(PongPacket { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> PongPacket<B> {
|
||||
pub fn time(&self) -> i64 {
|
||||
i64::from_be_bytes(self.buffer.as_ref()[..8].try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> PongPacket<B> {
|
||||
pub fn set_time(&mut self, time: i64) {
|
||||
self.buffer.as_mut()[..8].copy_from_slice(&time.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for PongPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PongPacket")
|
||||
.field("time", &self.time())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type TurnPongPacket<B> = TurnPingPacket<B>;
|
||||
|
||||
/// 探测目标延迟
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TurnPingPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TurnPingPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<TurnPingPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
if len != 16 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(TurnPingPacket { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TurnPingPacket<B> {
|
||||
// pub fn source(&self) -> Ipv4Addr {
|
||||
// let tmp:[u8;4] = self.buffer.as_ref()[..4].try_into().unwrap();
|
||||
// Ipv4Addr::from(tmp)
|
||||
// }
|
||||
// pub fn destination(&self) -> Ipv4Addr {
|
||||
// let tmp:[u8;4] = self.buffer.as_ref()[4..8].try_into().unwrap();
|
||||
// Ipv4Addr::from(tmp)
|
||||
// }
|
||||
pub fn time(&self) -> i64 {
|
||||
i64::from_be_bytes(self.buffer.as_ref()[8..].try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> TurnPingPacket<B> {
|
||||
pub fn set_source(&mut self, source: Ipv4Addr) {
|
||||
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
|
||||
}
|
||||
pub fn set_destination(&mut self, destination: Ipv4Addr) {
|
||||
self.buffer.as_mut()[4..8].copy_from_slice(&destination.octets());
|
||||
}
|
||||
pub fn set_time(&mut self, time: i64) {
|
||||
self.buffer.as_mut()[8..].copy_from_slice(&time.to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub type PunchResponsePacket<B> = PunchPacket<B>;
|
||||
pub type PunchRequestPacket<B> = PunchPacket<B>;
|
||||
|
||||
/// nat穿透
|
||||
#[derive(Clone)]
|
||||
pub struct PunchPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> PunchPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<PunchPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
if len != 8 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> PunchPacket<B> {
|
||||
pub fn source(&self) -> Ipv4Addr {
|
||||
let tmp: [u8; 4] = self.buffer.as_ref()[..4].try_into().unwrap();
|
||||
Ipv4Addr::from(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> PunchPacket<B> {
|
||||
pub fn set_source(&mut self, source: Ipv4Addr) {
|
||||
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for PunchPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PunchPacket")
|
||||
.field("source", &self.source())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use crate::error::*;
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Protocol {
|
||||
TokenError,
|
||||
Disconnect,
|
||||
Other(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Self::TokenError,
|
||||
2 => Self::Disconnect,
|
||||
val => Self::Other(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Protocol::TokenError => 1,
|
||||
Protocol::Disconnect => 2,
|
||||
Protocol::Other(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InErrorPacket<B> {
|
||||
TokenError,
|
||||
Disconnect,
|
||||
OtherError(ErrorPacket<B>),
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> InErrorPacket<B> {
|
||||
pub fn new(protocol: u8, buffer: B) -> Result<InErrorPacket<B>> {
|
||||
match Protocol::from(protocol) {
|
||||
Protocol::TokenError => Ok(InErrorPacket::TokenError),
|
||||
Protocol::Disconnect => Ok(InErrorPacket::Disconnect),
|
||||
Protocol::Other(_) => Ok(InErrorPacket::OtherError(ErrorPacket::new(buffer)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ErrorPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> ErrorPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<ErrorPacket<B>> {
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> ErrorPacket<B> {
|
||||
pub fn message(&self) -> Result<String> {
|
||||
match String::from_utf8(self.buffer.as_ref().to_vec()) {
|
||||
Ok(str) => Ok(str),
|
||||
Err(_) => Err(Error::InvalidPacket),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> ErrorPacket<B> {
|
||||
pub fn set_message(&mut self, message: &str) {
|
||||
self.buffer.as_mut().copy_from_slice(message.as_bytes())
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
pub mod control_packet;
|
||||
pub mod error_packet;
|
||||
pub mod service_packet;
|
||||
pub mod turn_packet;
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Version {
|
||||
V1,
|
||||
UnKnow(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Version {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Version::V1,
|
||||
val => Version::UnKnow(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Version {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Version::V1 => 1,
|
||||
Version::UnKnow(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Protocol {
|
||||
/// 服务包 用于和服务端交互
|
||||
Service,
|
||||
/// 响应异常
|
||||
Error,
|
||||
/// 控制协议
|
||||
Control,
|
||||
/// 转发ipv4数据
|
||||
Ipv4Turn,
|
||||
OtherTurn,
|
||||
UnKnow(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Protocol::Service,
|
||||
2 => Protocol::Error,
|
||||
3 => Protocol::Control,
|
||||
4 => Protocol::Ipv4Turn,
|
||||
5 => Protocol::OtherTurn,
|
||||
val => Protocol::UnKnow(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Protocol::Service => 1,
|
||||
Protocol::Error => 2,
|
||||
Protocol::Control => 3,
|
||||
Protocol::Ipv4Turn => 4,
|
||||
Protocol::OtherTurn => 5,
|
||||
Protocol::UnKnow(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NetPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> NetPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<NetPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
// 不能大于udp最大载荷长度
|
||||
if len < 4 || len > 65535 - 20 - 8 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(NetPacket { buffer })
|
||||
}
|
||||
pub fn buffer(&self) -> &[u8] {
|
||||
self.buffer.as_ref()
|
||||
}
|
||||
pub fn into_buffer(self) -> B {
|
||||
self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> NetPacket<B> {
|
||||
pub fn version(&self) -> Version {
|
||||
Version::from(self.buffer.as_ref()[0])
|
||||
}
|
||||
pub fn protocol(&self) -> Protocol {
|
||||
Protocol::from(self.buffer.as_ref()[1])
|
||||
}
|
||||
pub fn transport_protocol(&self) -> u8 {
|
||||
self.buffer.as_ref()[2]
|
||||
}
|
||||
pub fn ttl(&self) -> u8 {
|
||||
self.buffer.as_ref()[3]
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[4..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> NetPacket<B> {
|
||||
pub fn set_version(&mut self, version: Version) {
|
||||
self.buffer.as_mut()[0] = version.into();
|
||||
}
|
||||
pub fn set_protocol(&mut self, protocol: Protocol) {
|
||||
self.buffer.as_mut()[1] = protocol.into();
|
||||
}
|
||||
pub fn set_transport_protocol(&mut self, transport_protocol: u8) {
|
||||
self.buffer.as_mut()[2] = transport_protocol;
|
||||
}
|
||||
pub fn set_ttl(&mut self, ttl: u8) {
|
||||
self.buffer.as_mut()[3] = ttl;
|
||||
}
|
||||
pub fn set_payload(&mut self, payload: &[u8]) {
|
||||
self.buffer.as_mut()[4..payload.len() + 4].copy_from_slice(payload);
|
||||
}
|
||||
pub fn payload_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer.as_mut()[4..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for NetPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NetPacket")
|
||||
.field("version", &self.version())
|
||||
.field("protocol", &self.protocol())
|
||||
.field("transport_protocol", &self.transport_protocol())
|
||||
.field("ttl", &self.ttl())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Protocol {
|
||||
/// 注册请求
|
||||
RegistrationRequest,
|
||||
/// 注册响应
|
||||
RegistrationResponse,
|
||||
/// 更新设备列表
|
||||
UpdateDeviceList,
|
||||
UnKnow(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Self::RegistrationRequest,
|
||||
2 => Self::RegistrationResponse,
|
||||
3 => Self::UpdateDeviceList,
|
||||
val => Self::UnKnow(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Self::RegistrationRequest => 1,
|
||||
Self::RegistrationResponse => 2,
|
||||
Self::UpdateDeviceList => 3,
|
||||
Self::UnKnow(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Protocol {
|
||||
Punch,
|
||||
UnKnow(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Protocol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Protocol::Punch,
|
||||
val => Protocol::UnKnow(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for Protocol {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Protocol::Punch => 1,
|
||||
Protocol::UnKnow(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TurnPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TurnPacket<B> {
|
||||
pub fn new(buffer: B) -> Result<TurnPacket<B>> {
|
||||
let len = buffer.as_ref().len();
|
||||
if len <= 8 {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> TurnPacket<B> {
|
||||
pub fn source(&self) -> Ipv4Addr {
|
||||
let tmp: [u8; 4] = self.buffer.as_ref()[..4].try_into().unwrap();
|
||||
Ipv4Addr::from(tmp)
|
||||
}
|
||||
pub fn destination(&self) -> Ipv4Addr {
|
||||
let tmp: [u8; 4] = self.buffer.as_ref()[4..8].try_into().unwrap();
|
||||
Ipv4Addr::from(tmp)
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[8..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> TurnPacket<B> {
|
||||
pub fn payload_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer.as_mut()[8..]
|
||||
}
|
||||
pub fn set_source(&mut self, source: Ipv4Addr) {
|
||||
self.buffer.as_mut()[..4].copy_from_slice(&source.octets());
|
||||
}
|
||||
pub fn set_destination(&mut self, destination: Ipv4Addr) {
|
||||
self.buffer.as_mut()[4..8].copy_from_slice(&destination.octets());
|
||||
}
|
||||
pub fn set_payload(&mut self, payload: &[u8]) {
|
||||
self.buffer.as_mut()[8..payload.len() + 8].copy_from_slice(payload)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]>> fmt::Debug for TurnPacket<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TurnPacket")
|
||||
.field("source", &self.source())
|
||||
.field("destination", &self.destination())
|
||||
.field("payload", &self.payload())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
#[cfg(any(unix))]
|
||||
pub use unix::create_tun;
|
||||
#[cfg(any(unix))]
|
||||
pub use unix::{TunReader, TunWriter};
|
||||
|
||||
#[cfg(any(unix))]
|
||||
pub mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::create_tun;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{TunReader, TunWriter};
|
||||
@@ -1,156 +0,0 @@
|
||||
use std::io;
|
||||
use std::io::{Error, Read, Write};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
use bytes::BufMut;
|
||||
use tun::Device;
|
||||
use tun::platform::posix::{Reader, Writer};
|
||||
|
||||
pub fn create_tun(
|
||||
address: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
gateway: Ipv4Addr,
|
||||
) -> crate::error::Result<(TunWriter, TunReader)> {
|
||||
let mut config = tun::Configuration::default();
|
||||
|
||||
config
|
||||
.destination(gateway)
|
||||
.address(address)
|
||||
.netmask(netmask)
|
||||
.mtu(1420)
|
||||
.up();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
config.platform(|config| {
|
||||
config.packet_information(true);
|
||||
});
|
||||
|
||||
let dev = tun::create(&config).unwrap();
|
||||
|
||||
// let up_eth_str: String = format!("ifconfig utun3 {:?} {:?} up ", address, gateway);
|
||||
let route_add_str: String = format!(
|
||||
"sudo route -n add -net {:?} -netmask {:?} {:?}",
|
||||
address, netmask, gateway
|
||||
);
|
||||
//
|
||||
// let up_eth_out = Command::new("sh")
|
||||
// .arg("-c")
|
||||
// .arg(up_eth_str)
|
||||
// .output()
|
||||
// .expect("sh exec error!");
|
||||
// if !up_eth_out.status.success() {
|
||||
// return Err(crate::error::Error::Stop(format!("设置地址失败:{:?}", up_eth_out)));
|
||||
// }
|
||||
// println!("{:?}", up_eth_out);
|
||||
let if_config_out = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(route_add_str)
|
||||
.output()
|
||||
.expect("sh exec error!");
|
||||
if !if_config_out.status.success() {
|
||||
return Err(crate::error::Error::Stop(format!("设置路由失败:{:?}", if_config_out)));
|
||||
}
|
||||
// println!("{:?}", if_config_out);
|
||||
// let cmd_str: String = " ifconfig|grep flags=8051|awk -F ':' '{print $1}'|tail -1".to_string();
|
||||
//
|
||||
// let cmd_str_out = Command::new("sh")
|
||||
// .arg("-c")
|
||||
// .arg(cmd_str)
|
||||
// .output()
|
||||
// .expect("sh exec error!");
|
||||
// if !cmd_str_out.status.success(){
|
||||
// return Err(Error::Stop(format!("设置路由失败:{:?}", cmd_str_out)));
|
||||
// }
|
||||
// println!("{:?}", cmd_str_out);
|
||||
let packet_information = dev.has_packet_information();
|
||||
let (reader, writer) = dev.split();
|
||||
Ok((
|
||||
TunWriter(writer, packet_information),
|
||||
TunReader(reader, packet_information),
|
||||
))
|
||||
}
|
||||
|
||||
pub struct TunReader(Reader, bool);
|
||||
|
||||
impl TunReader {
|
||||
pub fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> io::Result<&mut [u8]> {
|
||||
let len = self.0.read(buf)?;
|
||||
if self.1 {
|
||||
Ok(&mut buf[4..len])
|
||||
} else {
|
||||
Ok(&mut buf[..len])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TunWriter(Writer, bool);
|
||||
|
||||
impl TunWriter {
|
||||
pub fn write(&mut self, packet: &[u8]) -> io::Result<()> {
|
||||
if self.1 {
|
||||
let mut buf = Vec::<u8>::with_capacity(4 + packet.len());
|
||||
buf.put_u16(0);
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
buf.put_u16(libc::PF_INET as u16);
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
buf.put_u16(libc::ETH_P_IP as u16);
|
||||
buf.extend_from_slice(packet);
|
||||
self.0.write_all(&buf)
|
||||
} else {
|
||||
self.0.write_all(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn main1() {
|
||||
// loop {
|
||||
// let len = reader.read(&mut buffer).unwrap();
|
||||
// println!("{:?}", &buffer[..len]);
|
||||
// match ip::Packet::new(&buffer[4..len]) {
|
||||
// Ok(ip::Packet::V4(pkt)) => {
|
||||
// match icmp::Packet::new(pkt.payload()) {
|
||||
// Ok(icmp) => {
|
||||
// match icmp.echo() {
|
||||
// Ok(icmp) => {
|
||||
// println!("{:?}", icmp);
|
||||
// let reply = ip::v4::Builder::default()
|
||||
// .id(0x42)
|
||||
// .unwrap()
|
||||
// .ttl(64)
|
||||
// .unwrap()
|
||||
// .source(pkt.destination())
|
||||
// .unwrap()
|
||||
// .destination(pkt.source())
|
||||
// .unwrap()
|
||||
// .icmp()
|
||||
// .unwrap()
|
||||
// .echo()
|
||||
// .unwrap()
|
||||
// .reply()
|
||||
// .unwrap()
|
||||
// .identifier(icmp.identifier())
|
||||
// .unwrap()
|
||||
// .sequence(icmp.sequence())
|
||||
// .unwrap()
|
||||
// .payload(icmp.payload())
|
||||
// .unwrap()
|
||||
// .build()
|
||||
// .unwrap();
|
||||
// let l = reply.len();
|
||||
// &mut buffer[4..(l + 4)].copy_from_slice(&reply);
|
||||
// // writer.write_all(&buffer[..4]).unwrap();
|
||||
// writer.write_all(&buffer[..(l + 4)]).unwrap();
|
||||
// }
|
||||
// Err(_) => {}
|
||||
// }
|
||||
// }
|
||||
// Err(_) => {}
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,132 +0,0 @@
|
||||
use std::io;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use libloading::Library;
|
||||
use wintun::{Adapter, Packet, Session};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
pub struct TunWriter(Arc<Session>);
|
||||
|
||||
impl TunWriter {
|
||||
pub fn write(&self, buf: &[u8]) -> io::Result<()> {
|
||||
match self.0.allocate_send_packet(buf.len() as u16) {
|
||||
Ok(mut packet) => {
|
||||
packet.bytes_mut().copy_from_slice(buf);
|
||||
self.0.send_packet(packet);
|
||||
return Ok(());
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "send err"));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TunReader(Arc<Session>);
|
||||
|
||||
impl TunReader {
|
||||
pub fn next(&self) -> io::Result<Packet> {
|
||||
match self.0.receive_blocking() {
|
||||
Ok(packet) => {
|
||||
return Ok(packet);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "read err"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_tun(
|
||||
address: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
gateway: Ipv4Addr,
|
||||
) -> Result<(TunWriter, TunReader)> {
|
||||
let win_tun = unsafe {
|
||||
match Library::new("wintun.dll") {
|
||||
Ok(library) => match wintun::load_from_library(library) {
|
||||
Ok(win_tun) => win_tun,
|
||||
Err(e) => {
|
||||
return Err(Error::Stop(format!("{:?}", e)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("{}", console::style("wintun.dll not found").red());
|
||||
return Err(Error::Stop(format!("{:?}", e)));
|
||||
}
|
||||
}
|
||||
};
|
||||
let adapter = match Adapter::open(&win_tun, "Demo") {
|
||||
Ok(a) => a,
|
||||
Err(_) => match Adapter::create(&win_tun, "Example", "Demo", None) {
|
||||
Ok(adapter) => adapter,
|
||||
|
||||
Err(e) => return Err(Error::Stop(format!("{:?}", e))),
|
||||
},
|
||||
};
|
||||
let index = adapter.get_adapter_index().unwrap();
|
||||
let set_mtu = format!(
|
||||
"netsh interface ipv4 set subinterface {} mtu=1420 store=persistent",
|
||||
index
|
||||
);
|
||||
let set_metric = format!("netsh interface ip set interface {} metric=1", index);
|
||||
let set_address = format!(
|
||||
"netsh interface ip set address {} static {:?} {:?} ", // gateway={:?}
|
||||
index, address, netmask,
|
||||
);
|
||||
// println!("{}", set_mtu);
|
||||
// println!("{}", set_metric);
|
||||
// println!("{}", set_address);
|
||||
// 执行网卡初始化命令
|
||||
let out = std::process::Command::new("cmd")
|
||||
.arg("/C")
|
||||
.arg(set_mtu)
|
||||
.output()
|
||||
.unwrap();
|
||||
if !out.status.success() {
|
||||
return Err(Error::Stop(format!("设置mtu失败:{:?}", out)));
|
||||
}
|
||||
let out = std::process::Command::new("cmd")
|
||||
.arg("/C")
|
||||
.arg(set_metric)
|
||||
.output()
|
||||
.unwrap();
|
||||
if !out.status.success() {
|
||||
return Err(Error::Stop(format!("设置接口跃点失败:{:?}", out)));
|
||||
}
|
||||
let out = std::process::Command::new("cmd")
|
||||
.arg("/C")
|
||||
.arg(set_address)
|
||||
.output()
|
||||
.unwrap();
|
||||
if !out.status.success() {
|
||||
return Err(Error::Stop(format!("设置网络地址失败:{:?}", out)));
|
||||
}
|
||||
let dest = {
|
||||
let ip = address.octets();
|
||||
let mask = netmask.octets();
|
||||
Ipv4Addr::from([
|
||||
ip[0] & mask[0],
|
||||
ip[1] & mask[1],
|
||||
ip[2] & mask[2],
|
||||
ip[3] & mask[3],
|
||||
])
|
||||
};
|
||||
let set_route = format!(
|
||||
"route add {:?} mask {:?} {:?} if {}",
|
||||
dest, netmask, gateway, index
|
||||
);
|
||||
// println!("{}", set_route);
|
||||
// 执行添加路由命令
|
||||
let out = std::process::Command::new("cmd")
|
||||
.arg("/C")
|
||||
.arg(set_route)
|
||||
.output()
|
||||
.unwrap();
|
||||
if !out.status.success() {
|
||||
return Err(Error::Stop(format!("添加路由失败:{:?}", out)));
|
||||
}
|
||||
let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY).unwrap());
|
||||
let reader_session = session.clone();
|
||||
Ok((TunWriter(session), TunReader(reader_session)))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
[package]
|
||||
name = "vnt-core"
|
||||
version = "2.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
futures = "0.3"
|
||||
|
||||
tun-rs = { version = "2", features = ["async", "async_framed"] }
|
||||
|
||||
rust-p2p-core = { version="0.4" }
|
||||
tcp_ip = { git = "https://github.com/rustp2p/tcp_ip" }
|
||||
|
||||
anyhow = "1"
|
||||
parking_lot = "0.12"
|
||||
|
||||
quinn = { version = "0.11", default-features = false, features = ["rustls", "runtime-tokio"] }
|
||||
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring"] }
|
||||
hex = "0.4"
|
||||
sha2 = "0.10"
|
||||
rustls-native-certs = "0.8.2"
|
||||
log = "0.4"
|
||||
bytes = "1.11.0"
|
||||
rand = "0.9"
|
||||
time = { version = "0.3", features = ["macros", "formatting", "local-offset"] }
|
||||
pnet_packet = "0.35"
|
||||
ring = "0.17.14"
|
||||
|
||||
prost = "0.14"
|
||||
tokio-tungstenite = "0.28.0"
|
||||
tungstenite = "0.28.0"
|
||||
uuid = { version = "1.18.1", features = ["v4"] }
|
||||
ipnet = { version = "2.11", features = ["serde"] }
|
||||
getifaddrs = "0.6.0"
|
||||
dns-parser = "0.8"
|
||||
|
||||
lz4_flex = "0.12.0"
|
||||
reed-solomon-erasure = "6.0"
|
||||
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
rcgen = "0.14.6"
|
||||
machine-uid = "0.5.4"
|
||||
|
||||
zerocopy = { version = "0.8.31", features = ["derive"] }
|
||||
socket2 = { version = "0.6.1", features = ["all"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winreg"] }
|
||||
widestring = "1.2"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.14"
|
||||
@@ -0,0 +1,15 @@
|
||||
fn main() {
|
||||
let mut config = prost_build::Config::new();
|
||||
config.protoc_arg("--experimental_allow_proto3_optional");
|
||||
config
|
||||
.compile_protos(
|
||||
&[
|
||||
"proto/control_message.proto",
|
||||
"proto/rpc.proto",
|
||||
"proto/client.proto",
|
||||
"proto/fec.proto",
|
||||
],
|
||||
&["proto"],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package protocol.client;
|
||||
|
||||
message QuicProxyHandshake{
|
||||
oneof handshake{
|
||||
TcpProxyHandshake tcp = 1;
|
||||
IpProxyHandshake ip = 2;
|
||||
PortProxyHandshake tcp_port_mapping = 3;
|
||||
PortProxyHandshake udp_port_mapping = 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
message TcpProxyHandshake{
|
||||
fixed32 src_ip = 1;
|
||||
uint32 src_port = 2;
|
||||
fixed32 dst_ip = 3;
|
||||
uint32 dst_port = 4;
|
||||
}
|
||||
message PortProxyHandshake{
|
||||
string src_ip = 1;
|
||||
uint32 src_port = 2;
|
||||
string dst_host = 3;
|
||||
uint32 dst_port = 4;
|
||||
}
|
||||
|
||||
message IpProxyHandshake{
|
||||
uint32 ip_next_header_protocol = 1;
|
||||
fixed32 src_ip = 2;
|
||||
fixed32 dst_ip = 3;
|
||||
}
|
||||
|
||||
enum NatType {
|
||||
NAT_TYPE_CONE = 0;
|
||||
NAT_TYPE_SYMMETRIC = 1;
|
||||
}
|
||||
message NatInfo {
|
||||
|
||||
NatType nat_type = 1;
|
||||
|
||||
repeated fixed32 public_ips = 2;
|
||||
|
||||
repeated uint32 public_udp_ports = 3;
|
||||
|
||||
uint32 public_port_range = 4;
|
||||
|
||||
repeated fixed32 local_ipv4s = 5;
|
||||
|
||||
optional bytes ipv6 = 6;
|
||||
|
||||
repeated uint32 local_udp_ports = 7;
|
||||
|
||||
uint32 local_tcp_port = 8;
|
||||
|
||||
uint32 public_tcp_port = 9;
|
||||
}
|
||||
|
||||
message PunchInfo{
|
||||
NatInfo nat_info = 1;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package protocol.control_message;
|
||||
|
||||
enum RegistrationMode {
|
||||
NORMAL = 0;
|
||||
PRE_REGISTER = 1;
|
||||
}
|
||||
|
||||
message RegRequestMsg {
|
||||
string network_code = 1;
|
||||
string device_id = 2;
|
||||
optional fixed32 ip = 3;
|
||||
string name = 4;
|
||||
string version = 5;
|
||||
optional string key_sign = 6;
|
||||
bool ip_variable = 7;
|
||||
fixed32 server_id = 8;
|
||||
RegistrationMode registration_mode = 9;
|
||||
}
|
||||
|
||||
|
||||
message RegResponseMsg {
|
||||
fixed32 ip = 1;
|
||||
uint32 prefix_len = 2;
|
||||
fixed32 gateway = 3;
|
||||
string server_version = 4;
|
||||
}
|
||||
|
||||
message ConfirmRegMsg {
|
||||
}
|
||||
|
||||
message ConfirmRegResponseMsg {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message ErrorResponseMsg {
|
||||
uint32 code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message RequestMessage{
|
||||
oneof request_payload{
|
||||
RegRequestMsg reg = 1;
|
||||
ConfirmRegMsg confirm_reg = 2;
|
||||
}
|
||||
}
|
||||
message ResponseMessage{
|
||||
oneof response_payload{
|
||||
RegResponseMsg reg = 1;
|
||||
ErrorResponseMsg error = 2;
|
||||
ConfirmRegResponseMsg confirm_reg = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message SelectiveBroadcast {
|
||||
repeated fixed32 ips = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message ClientSimpleInfo{
|
||||
fixed32 ip = 1;
|
||||
bool online = 2;
|
||||
}
|
||||
|
||||
message ClientSimpleInfoList{
|
||||
uint64 data_version = 1;
|
||||
repeated ClientSimpleInfo list = 2;
|
||||
bool is_all = 3;
|
||||
int64 time = 4;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package protocol.fec;
|
||||
|
||||
message FecPacket {
|
||||
uint64 group_id = 1; // FEC 组号(按目标IP分组)
|
||||
uint32 packet_index = 2; // 包在组内的序号(0-based)
|
||||
bytes payload = 3;
|
||||
optional ParityData parity_data = 4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 冗余包(携带FEC恢复所需的元数据)
|
||||
message ParityData {
|
||||
uint32 data_shards = 1; // 原始包数量
|
||||
uint32 parity_shards = 2; // 冗余包数量
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package protocol.rpc;
|
||||
|
||||
message RpcMessageRequest{
|
||||
uint64 id = 1;
|
||||
oneof rpc_req_payload{
|
||||
ClientListRequest client_list_req = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ClientListRequest{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
message RpcMessageResponse{
|
||||
uint64 id = 1;
|
||||
oneof rpc_res_payload{
|
||||
ClientListResponse client_list_res = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ClientInfo{
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
fixed32 ip = 3;
|
||||
optional string key_sign = 4;
|
||||
bool online = 5;
|
||||
int64 last_connected_time = 6;
|
||||
string id = 7;
|
||||
}
|
||||
|
||||
message ClientListResponse{
|
||||
repeated ClientInfo list = 1;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::context::config::Config;
|
||||
use crate::context::{AppState, NetworkAddr, PacketLossInfo, ServerNodeInfo, TrafficInfo};
|
||||
use crate::protocol::control_message::ClientSimpleInfo;
|
||||
use crate::tunnel_core::p2p::route_table::Route;
|
||||
use crate::tunnel_core::server::rpc::ServerRPC;
|
||||
use rust_p2p_core::nat::NatInfo;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VntApi {
|
||||
app_state: AppState,
|
||||
server_rpc: ServerRPC,
|
||||
}
|
||||
|
||||
impl VntApi {
|
||||
pub(crate) fn new(app_state: AppState, server_rpc: ServerRPC) -> Self {
|
||||
Self {
|
||||
app_state,
|
||||
server_rpc,
|
||||
}
|
||||
}
|
||||
pub fn server_rpc(&self) -> &ServerRPC {
|
||||
&self.server_rpc
|
||||
}
|
||||
/// 获取启动配置
|
||||
pub fn get_config(&self) -> Option<Box<Config>> {
|
||||
self.app_state.get_config()
|
||||
}
|
||||
/// 获取所有客户端ip
|
||||
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
|
||||
self.app_state.client_ips()
|
||||
}
|
||||
/// 判断目标IP是否直连
|
||||
pub fn is_direct(&self, ip: &Ipv4Addr) -> bool {
|
||||
self.app_state.route_table.p2p_num(ip) > 0
|
||||
}
|
||||
/// 查找路由
|
||||
pub fn find_route(&self, ip: &Ipv4Addr) -> Option<Route> {
|
||||
self.app_state.route_table.get_route_by_id(ip).ok()
|
||||
}
|
||||
pub fn get_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
|
||||
if let Some(route) = self.find_route(ip) {
|
||||
Some(route.rtt())
|
||||
} else {
|
||||
self.server_node_rtt(ip).map(|v| v * 2)
|
||||
}
|
||||
}
|
||||
/// 获取所有路由
|
||||
pub fn route_table(&self) -> Vec<(Ipv4Addr, Vec<Route>)> {
|
||||
self.app_state.route_table.route_table()
|
||||
}
|
||||
/// 获取服务器节点
|
||||
pub fn server_node_list(&self) -> Vec<ServerNodeInfo> {
|
||||
self.app_state.server_info_collection.server_node_list()
|
||||
}
|
||||
pub fn server_node_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
|
||||
self.app_state.server_info_collection.get_server_rtt(ip)
|
||||
}
|
||||
/// 获取网络配置
|
||||
pub fn network(&self) -> Option<NetworkAddr> {
|
||||
self.app_state.get_network()
|
||||
}
|
||||
/// 获取当前的nat信息
|
||||
pub fn nat_info(&self) -> Option<NatInfo> {
|
||||
self.app_state.get_nat_info()
|
||||
}
|
||||
pub fn peer_nat_info(&self, ip: &Ipv4Addr) -> Option<NatInfo> {
|
||||
self.app_state.get_peer_info(ip).and_then(|v| v.nat_info)
|
||||
}
|
||||
pub fn packet_loss_info(&self, ip: &Ipv4Addr) -> Option<PacketLossInfo> {
|
||||
self.app_state.packet_loss_stats.get_loss_info(ip)
|
||||
}
|
||||
pub fn all_packet_loss_info(&self) -> Vec<PacketLossInfo> {
|
||||
self.app_state.packet_loss_stats.get_all_loss_info()
|
||||
}
|
||||
pub fn reset_packet_loss(&self, ip: &Ipv4Addr) {
|
||||
self.app_state.packet_loss_stats.reset(ip)
|
||||
}
|
||||
pub fn reset_all_packet_loss(&self) {
|
||||
self.app_state.packet_loss_stats.reset_all()
|
||||
}
|
||||
pub fn traffic_info(&self, ip: &Ipv4Addr) -> Option<TrafficInfo> {
|
||||
self.app_state.traffic_stats.get_traffic_info(ip)
|
||||
}
|
||||
pub fn all_traffic_info(&self) -> Vec<TrafficInfo> {
|
||||
self.app_state.traffic_stats.get_all_traffic_info()
|
||||
}
|
||||
pub fn reset_traffic(&self, ip: &Ipv4Addr) {
|
||||
self.app_state.traffic_stats.reset(ip)
|
||||
}
|
||||
pub fn reset_all_traffic(&self) {
|
||||
self.app_state.traffic_stats.reset_all()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use std::io;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LZ4Compression {
|
||||
min_size: usize, // 只压缩大于此大小的数据包
|
||||
}
|
||||
|
||||
impl LZ4Compression {
|
||||
pub fn new() -> Self {
|
||||
Self::with_min_size(256)
|
||||
}
|
||||
pub fn with_min_size(min_size: usize) -> Self {
|
||||
Self { min_size }
|
||||
}
|
||||
|
||||
/// 压缩数据包,返回新的压缩后的数据包
|
||||
/// reserve: 尾部预留空间(用于后续加密等操作)
|
||||
pub fn compress(
|
||||
&self,
|
||||
pkt: NetPacket<TransmissionBytes>,
|
||||
reserve: usize,
|
||||
) -> io::Result<NetPacket<TransmissionBytes>> {
|
||||
let payload = pkt.payload();
|
||||
if payload.len() < self.min_size {
|
||||
return Ok(pkt);
|
||||
}
|
||||
let compressed = lz4_flex::compress_prepend_size(payload);
|
||||
if compressed.len() >= payload.len() {
|
||||
return Ok(pkt);
|
||||
}
|
||||
let total_len = HEAD_LENGTH + compressed.len();
|
||||
let mut buf = TransmissionBytes::zeroed_size(total_len, reserve);
|
||||
|
||||
buf[..HEAD_LENGTH].copy_from_slice(&pkt.buffer()[..HEAD_LENGTH]);
|
||||
buf[HEAD_LENGTH..total_len].copy_from_slice(&compressed);
|
||||
|
||||
let mut packet = NetPacket::new(buf)?;
|
||||
packet.set_compressed_flag(true);
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
/// 解压缩数据包,返回新的解压后的数据包
|
||||
/// reserve: 尾部预留空间(用于后续加密等操作)
|
||||
pub fn decompress(
|
||||
&self,
|
||||
pkt: NetPacket<TransmissionBytes>,
|
||||
) -> io::Result<NetPacket<TransmissionBytes>> {
|
||||
if !pkt.is_compressed() {
|
||||
return Ok(pkt);
|
||||
}
|
||||
let payload = pkt.payload();
|
||||
|
||||
let decompressed = lz4_flex::decompress_size_prepended(payload).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("decompress failed: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
let total_len = HEAD_LENGTH + decompressed.len();
|
||||
let mut buf = TransmissionBytes::zeroed(total_len);
|
||||
|
||||
buf[..HEAD_LENGTH].copy_from_slice(&pkt.buffer()[..HEAD_LENGTH]);
|
||||
buf[HEAD_LENGTH..total_len].copy_from_slice(&decompressed);
|
||||
|
||||
let mut packet = NetPacket::new(buf)?;
|
||||
packet.set_compressed_flag(false);
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
|
||||
fn make_packet(data: &[u8]) -> NetPacket<TransmissionBytes> {
|
||||
let mut buf = TransmissionBytes::zeroed(HEAD_LENGTH + data.len());
|
||||
buf[HEAD_LENGTH..HEAD_LENGTH + data.len()].copy_from_slice(data);
|
||||
NetPacket::new(buf).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lz4_compress_and_decompress() {
|
||||
let lz = LZ4Compression::with_min_size(10);
|
||||
|
||||
// --- 构造原始包 ---
|
||||
let payload = vec![1u8; 200];
|
||||
let original = make_packet(&payload);
|
||||
|
||||
// --- 压缩 ---
|
||||
let compressed = lz.compress(original, 0).unwrap();
|
||||
assert!(compressed.is_compressed());
|
||||
|
||||
// 压缩后的 payload 应变小
|
||||
assert!(
|
||||
compressed.payload().len() < payload.len(),
|
||||
"压缩后 payload 应该更小"
|
||||
);
|
||||
|
||||
// --- 解压 ---
|
||||
let decompressed = lz.decompress(compressed).unwrap();
|
||||
|
||||
// 标志应清除
|
||||
assert!(!decompressed.is_compressed());
|
||||
|
||||
// HEAD 不变
|
||||
assert_eq!(
|
||||
&decompressed.buffer()[..HEAD_LENGTH],
|
||||
&[0u8; HEAD_LENGTH][..],
|
||||
"HEAD 必须保持不变"
|
||||
);
|
||||
|
||||
// payload 必须等于原始 payload
|
||||
assert_eq!(decompressed.payload(), &payload[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_compress_when_small() {
|
||||
let lz = LZ4Compression::with_min_size(100);
|
||||
|
||||
let pkt = make_packet(&[7; 20]);
|
||||
let compressed = lz.compress(pkt, 0).unwrap();
|
||||
|
||||
assert!(!compressed.is_compressed(), "小包不应该被压缩");
|
||||
|
||||
assert_eq!(compressed.payload(), &[7; 20][..]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::compression::lz4_compression::LZ4Compression;
|
||||
use crate::protocol::ip_packet_protocol::NetPacket;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use std::io;
|
||||
|
||||
mod lz4_compression;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketCompression {
|
||||
compression: Option<LZ4Compression>,
|
||||
}
|
||||
|
||||
impl PacketCompression {
|
||||
pub(crate) fn new(enabled: bool) -> Self {
|
||||
Self {
|
||||
compression: if enabled {
|
||||
Some(LZ4Compression::new())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compress(
|
||||
&self,
|
||||
pkt: NetPacket<TransmissionBytes>,
|
||||
reserve: usize,
|
||||
) -> io::Result<NetPacket<TransmissionBytes>> {
|
||||
if let Some(compression) = self.compression.as_ref() {
|
||||
return compression.compress(pkt, reserve);
|
||||
}
|
||||
|
||||
Ok(pkt)
|
||||
}
|
||||
|
||||
pub(crate) fn decompress(
|
||||
&self,
|
||||
pkt: NetPacket<TransmissionBytes>,
|
||||
) -> io::Result<NetPacket<TransmissionBytes>> {
|
||||
if let Some(compression) = self.compression.as_ref() {
|
||||
return compression.decompress(pkt);
|
||||
}
|
||||
Ok(pkt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::nat::NetInput;
|
||||
use crate::port_mapping::PortMapping;
|
||||
use crate::tls::verifier::CertValidationMode;
|
||||
use crate::tunnel_core::server::transport::config::{ConnectRegConfig, ProtocolAddress};
|
||||
use anyhow::bail;
|
||||
use ipnet::Ipv4Net;
|
||||
use std::collections::HashSet;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
pub const MAX_NETWORK_CODE_LEN: usize = 32;
|
||||
pub const MAX_DEVICE_ID_LEN: usize = 64;
|
||||
pub const MAX_NAME_LEN: usize = 128;
|
||||
pub const MAX_VERSION_LEN: usize = 32;
|
||||
pub const MAX_MTU: u16 = 1500;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Config {
|
||||
pub server_addr: Vec<ProtocolAddress>,
|
||||
pub cert_mode: CertValidationMode,
|
||||
pub network_code: String,
|
||||
pub device_id: String,
|
||||
pub device_name: String,
|
||||
pub tun_name: Option<String>,
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
pub password: Option<String>,
|
||||
pub no_punch: bool,
|
||||
pub compress: bool,
|
||||
pub rtx: bool,
|
||||
pub fec: bool,
|
||||
pub input: Vec<NetInput>,
|
||||
pub output: Vec<Ipv4Net>,
|
||||
pub no_nat: bool,
|
||||
pub no_tun: bool,
|
||||
pub mtu: Option<u16>,
|
||||
pub port_mapping: Vec<PortMapping>,
|
||||
pub allow_port_mapping: bool,
|
||||
pub udp_stun: Vec<String>,
|
||||
pub tcp_stun: Vec<String>,
|
||||
}
|
||||
impl Config {
|
||||
pub fn check(&self) -> anyhow::Result<()> {
|
||||
if self.server_addr.is_empty() {
|
||||
bail!("服务器地址不能为空");
|
||||
}
|
||||
if self.server_addr.len() > 1 {
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for a in self.server_addr.iter() {
|
||||
if !set.insert(a.address.as_str()) {
|
||||
bail!("服务器地址不能相同")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.network_code.len() > MAX_NETWORK_CODE_LEN {
|
||||
bail!(
|
||||
"network_code length exceeds {} characters (current: {})",
|
||||
MAX_NETWORK_CODE_LEN,
|
||||
self.network_code.len()
|
||||
)
|
||||
}
|
||||
|
||||
if self.device_id.len() > MAX_DEVICE_ID_LEN {
|
||||
bail!(
|
||||
"device_id length exceeds {} characters (current: {})",
|
||||
MAX_DEVICE_ID_LEN,
|
||||
self.device_id.len()
|
||||
)
|
||||
}
|
||||
|
||||
if self.device_name.len() > MAX_NAME_LEN {
|
||||
bail!(
|
||||
"name length exceeds {} characters (current: {})",
|
||||
MAX_NAME_LEN,
|
||||
self.device_name.len()
|
||||
)
|
||||
}
|
||||
if let Some(mtu) = self.mtu
|
||||
&& mtu > MAX_MTU
|
||||
{
|
||||
bail!("MTU is too large (Maximum mtu: {MAX_MTU})",)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn key_sign(&self) -> Option<String> {
|
||||
self.password.as_ref().map(|p| PacketCrypto::key_sign(p))
|
||||
}
|
||||
pub(crate) fn to_connect_config(&self, index: usize) -> ConnectRegConfig {
|
||||
ConnectRegConfig {
|
||||
server_addr: self.server_addr[index].clone(),
|
||||
cert_mode: self.cert_mode.clone(),
|
||||
network_code: self.network_code.clone(),
|
||||
device_id: self.device_id.clone(),
|
||||
device_name: self.device_name.clone(),
|
||||
ip: self.ip,
|
||||
key_sign: self.key_sign(),
|
||||
ip_variable: self.ip.is_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
use crate::context::config::Config;
|
||||
use crate::context::nat::{MyNatInfo, PunchBackoff};
|
||||
use crate::nat::SubnetExternalRoute;
|
||||
use crate::protocol::client_message::PunchInfo;
|
||||
use crate::protocol::control_message::{ClientSimpleInfo, ClientSimpleInfoList};
|
||||
use crate::tunnel_core::p2p::route_table::RouteTable;
|
||||
use crate::tunnel_core::server::transport::config::ProtocolAddress;
|
||||
use ipnet::Ipv4Net;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use rust_p2p_core::nat::NatInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
#[derive(Default)]
|
||||
struct PingStats {
|
||||
sent: u64,
|
||||
received: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TrafficCounter {
|
||||
tx_bytes: u64,
|
||||
rx_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TrafficStats {
|
||||
inner: Arc<RwLock<HashMap<Ipv4Addr, Arc<Mutex<TrafficCounter>>>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TrafficInfo {
|
||||
pub ip: Ipv4Addr,
|
||||
pub tx_bytes: u64,
|
||||
pub rx_bytes: u64,
|
||||
}
|
||||
|
||||
impl TrafficStats {
|
||||
fn get_or_create(&self, ip: Ipv4Addr) -> Arc<Mutex<TrafficCounter>> {
|
||||
{
|
||||
let read = self.inner.read();
|
||||
if let Some(counter) = read.get(&ip) {
|
||||
return counter.clone();
|
||||
}
|
||||
}
|
||||
let mut write = self.inner.write();
|
||||
write
|
||||
.entry(ip)
|
||||
.or_insert_with(|| Arc::new(Mutex::new(TrafficCounter::default())))
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn record_tx(&self, ip: Ipv4Addr, bytes: u64) {
|
||||
let counter = self.get_or_create(ip);
|
||||
counter.lock().tx_bytes += bytes;
|
||||
}
|
||||
|
||||
pub fn record_rx(&self, ip: Ipv4Addr, bytes: u64) {
|
||||
let counter = self.get_or_create(ip);
|
||||
counter.lock().rx_bytes += bytes;
|
||||
}
|
||||
|
||||
pub fn get_traffic_info(&self, ip: &Ipv4Addr) -> Option<TrafficInfo> {
|
||||
let read = self.inner.read();
|
||||
read.get(ip).map(|counter| {
|
||||
let guard = counter.lock();
|
||||
TrafficInfo {
|
||||
ip: *ip,
|
||||
tx_bytes: guard.tx_bytes,
|
||||
rx_bytes: guard.rx_bytes,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_traffic_info(&self) -> Vec<TrafficInfo> {
|
||||
let read = self.inner.read();
|
||||
read.iter()
|
||||
.map(|(ip, counter)| {
|
||||
let guard = counter.lock();
|
||||
TrafficInfo {
|
||||
ip: *ip,
|
||||
tx_bytes: guard.tx_bytes,
|
||||
rx_bytes: guard.rx_bytes,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn reset(&self, ip: &Ipv4Addr) {
|
||||
let read = self.inner.read();
|
||||
if let Some(counter) = read.get(ip) {
|
||||
*counter.lock() = TrafficCounter::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_all(&self) {
|
||||
let read = self.inner.read();
|
||||
for counter in read.values() {
|
||||
*counter.lock() = TrafficCounter::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.inner.write().clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PacketLossStats {
|
||||
inner: Arc<RwLock<HashMap<Ipv4Addr, Arc<Mutex<PingStats>>>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PacketLossInfo {
|
||||
pub ip: Ipv4Addr,
|
||||
pub sent: u64,
|
||||
pub received: u64,
|
||||
pub loss_rate: f64,
|
||||
}
|
||||
|
||||
impl PacketLossStats {
|
||||
fn get_or_create(&self, ip: Ipv4Addr) -> Arc<Mutex<PingStats>> {
|
||||
{
|
||||
let read = self.inner.read();
|
||||
if let Some(stats) = read.get(&ip) {
|
||||
return stats.clone();
|
||||
}
|
||||
}
|
||||
let mut write = self.inner.write();
|
||||
write
|
||||
.entry(ip)
|
||||
.or_insert_with(|| Arc::new(Mutex::new(PingStats::default())))
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn record_sent(&self, ip: Ipv4Addr) {
|
||||
let stats = self.get_or_create(ip);
|
||||
stats.lock().sent += 1;
|
||||
}
|
||||
|
||||
pub fn record_received(&self, ip: Ipv4Addr) {
|
||||
let stats = self.get_or_create(ip);
|
||||
stats.lock().received += 1;
|
||||
}
|
||||
|
||||
pub fn get_loss_info(&self, ip: &Ipv4Addr) -> Option<PacketLossInfo> {
|
||||
let read = self.inner.read();
|
||||
read.get(ip).map(|stats| {
|
||||
let guard = stats.lock();
|
||||
let loss_rate = if guard.sent > 0 {
|
||||
1.0 - (guard.received as f64 / guard.sent as f64)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
PacketLossInfo {
|
||||
ip: *ip,
|
||||
sent: guard.sent,
|
||||
received: guard.received,
|
||||
loss_rate,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_loss_info(&self) -> Vec<PacketLossInfo> {
|
||||
let read = self.inner.read();
|
||||
read.iter()
|
||||
.map(|(ip, stats)| {
|
||||
let guard = stats.lock();
|
||||
let loss_rate = if guard.sent > 0 {
|
||||
1.0 - (guard.received as f64 / guard.sent as f64)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
PacketLossInfo {
|
||||
ip: *ip,
|
||||
sent: guard.sent,
|
||||
received: guard.received,
|
||||
loss_rate,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn reset(&self, ip: &Ipv4Addr) {
|
||||
let read = self.inner.read();
|
||||
if let Some(stats) = read.get(ip) {
|
||||
*stats.lock() = PingStats::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_all(&self) {
|
||||
let read = self.inner.read();
|
||||
for stats in read.values() {
|
||||
*stats.lock() = PingStats::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.inner.write().clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod config;
|
||||
pub(crate) mod nat;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct AppState {
|
||||
config: Arc<Mutex<Option<Box<Config>>>>,
|
||||
pub(crate) network: SharedNetworkAddr,
|
||||
pub(crate) server_info_collection: ServerInfoCollection,
|
||||
pub(crate) peer_map: PeerInfoMap,
|
||||
pub(crate) route_table: RouteTable,
|
||||
pub(crate) subnet_route: SubnetExternalRoute,
|
||||
pub(crate) nat_info: MyNatInfo,
|
||||
pub(crate) punch_backoff: PunchBackoff,
|
||||
pub(crate) packet_loss_stats: PacketLossStats,
|
||||
pub(crate) traffic_stats: TrafficStats,
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct SharedNetworkAddr {
|
||||
inner: Arc<Mutex<Option<NetworkAddr>>>,
|
||||
}
|
||||
impl SharedNetworkAddr {
|
||||
pub fn network(&self) -> Option<Ipv4Net> {
|
||||
self.inner.lock().as_ref().map(|v| v.network())
|
||||
}
|
||||
pub fn ip(&self) -> Option<Ipv4Addr> {
|
||||
self.inner.lock().map(|v| v.ip)
|
||||
}
|
||||
pub fn get(&self) -> Option<NetworkAddr> {
|
||||
*self.inner.lock()
|
||||
}
|
||||
pub fn set(&self, addr: NetworkAddr) {
|
||||
*self.inner.lock() = Some(addr);
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
*self.inner.lock() = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// 网络路由封装,包含本地网络信息和子网路由
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct NetworkRoute {
|
||||
pub network: SharedNetworkAddr,
|
||||
pub subnet_route: SubnetExternalRoute,
|
||||
}
|
||||
|
||||
impl NetworkRoute {
|
||||
pub fn new(network: SharedNetworkAddr, subnet_route: SubnetExternalRoute) -> Self {
|
||||
Self {
|
||||
network,
|
||||
subnet_route,
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 IP 是否在本地网络或子网路由中
|
||||
pub fn network_contains(&self, ip: &Ipv4Addr) -> bool {
|
||||
if let Some(network) = self.network.network()
|
||||
&& network.contains(ip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.subnet_route.route(ip).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PeerClientInfo {
|
||||
pub nat_info: Option<NatInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct PeerInfoMap {
|
||||
inner: Arc<Mutex<HashMap<Ipv4Addr, PeerClientInfo>>>,
|
||||
}
|
||||
|
||||
impl PeerInfoMap {
|
||||
pub fn get(&self, ip: &Ipv4Addr) -> Option<PeerClientInfo> {
|
||||
self.inner.lock().get(ip).cloned()
|
||||
}
|
||||
|
||||
pub fn update_nat_info(&self, ip: Ipv4Addr, nat_info: NatInfo) {
|
||||
let mut guard = self.inner.lock();
|
||||
if let Some(v) = guard.get_mut(&ip) {
|
||||
v.nat_info = Some(nat_info);
|
||||
return;
|
||||
}
|
||||
guard.insert(
|
||||
ip,
|
||||
PeerClientInfo {
|
||||
nat_info: Some(nat_info),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.inner.lock().clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ServerInfoCollection {
|
||||
client_simple_list: Arc<RwLock<Vec<ClientSimpleInfo>>>,
|
||||
server_node_map: Arc<RwLock<HashMap<u32, ServerNodeInfo>>>,
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ServerNodeInfo {
|
||||
pub server_id: u32,
|
||||
pub server_addr: ProtocolAddress,
|
||||
pub connected: bool,
|
||||
pub rtt: Option<u32>,
|
||||
pub data_version: u64,
|
||||
pub client_map: HashMap<Ipv4Addr, ClientSimpleInfo>,
|
||||
pub last_connected_time: Option<i64>,
|
||||
pub disconnected_time: Option<i64>,
|
||||
pub server_version: Option<String>,
|
||||
}
|
||||
impl ServerInfoCollection {
|
||||
pub fn server_client_ip_map(&self) -> HashMap<u32, (Vec<Ipv4Addr>, u32)> {
|
||||
self.server_node_map
|
||||
.read()
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
*k,
|
||||
(
|
||||
v.client_map
|
||||
.iter()
|
||||
.filter(|(_, v)| v.online)
|
||||
.map(|(k, _)| *k)
|
||||
.collect(),
|
||||
v.rtt.unwrap_or(500),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub fn server_node_list(&self) -> Vec<ServerNodeInfo> {
|
||||
self.server_node_map.read().values().cloned().collect()
|
||||
}
|
||||
pub fn update_server(&self, addr: Vec<(u32, ProtocolAddress)>) {
|
||||
let mut server_node_map_guard = self.server_node_map.write();
|
||||
let mut client_simple_list_guard = self.client_simple_list.write();
|
||||
server_node_map_guard.clear();
|
||||
client_simple_list_guard.clear();
|
||||
for (server_id, server_addr) in addr {
|
||||
let server_node = ServerNodeInfo {
|
||||
server_id,
|
||||
server_addr,
|
||||
..Default::default()
|
||||
};
|
||||
server_node_map_guard.insert(server_id, server_node);
|
||||
}
|
||||
}
|
||||
pub fn find_connected_server(&self, server_ids: &[u32]) -> Option<u32> {
|
||||
let map = self.server_node_map.read();
|
||||
|
||||
server_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let server = map.get(id)?;
|
||||
|
||||
if !server.connected {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rtt = server.rtt.unwrap_or(u32::MAX);
|
||||
|
||||
Some((*id, rtt))
|
||||
})
|
||||
.min_by_key(|(_, rtt)| *rtt)
|
||||
.map(|(id, _)| id)
|
||||
}
|
||||
pub fn find_ip_to_server(&self, server_ids: &[u32], ip: &Ipv4Addr) -> Option<u32> {
|
||||
let map = self.server_node_map.read();
|
||||
|
||||
server_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let server = map.get(id)?;
|
||||
|
||||
if !server.connected {
|
||||
return None;
|
||||
}
|
||||
|
||||
let client = server.client_map.get(ip)?;
|
||||
if !client.online {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rtt = server.rtt.unwrap_or(u32::MAX);
|
||||
|
||||
Some((*id, rtt))
|
||||
})
|
||||
.min_by_key(|(_, rtt)| *rtt)
|
||||
.map(|(id, _)| id)
|
||||
}
|
||||
pub fn client_online_ips(&self) -> Vec<Ipv4Addr> {
|
||||
self.client_simple_list
|
||||
.read()
|
||||
.iter()
|
||||
.filter(|v| v.online)
|
||||
.map(|c| c.ip)
|
||||
.collect()
|
||||
}
|
||||
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
|
||||
self.client_simple_list.read().clone()
|
||||
}
|
||||
pub fn data_version(&self, server_id: u32) -> u64 {
|
||||
self.server_node_map
|
||||
.read()
|
||||
.get(&server_id)
|
||||
.map(|v| v.data_version)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
pub fn update_client_simple_list(
|
||||
&self,
|
||||
server_id: u32,
|
||||
self_ip: Ipv4Addr,
|
||||
client_simple_list: ClientSimpleInfoList,
|
||||
now: i64,
|
||||
) {
|
||||
let mut guard = self.server_node_map.write();
|
||||
let server_node = guard.entry(server_id).or_default();
|
||||
if now > client_simple_list.time {
|
||||
server_node.rtt = Some((now - client_simple_list.time) as u32);
|
||||
}
|
||||
server_node.data_version = client_simple_list.data_version;
|
||||
let map: HashMap<Ipv4Addr, ClientSimpleInfo> = client_simple_list
|
||||
.list
|
||||
.into_iter()
|
||||
.filter(|v| v.ip != self_ip)
|
||||
.map(|info| (info.ip, info))
|
||||
.collect();
|
||||
|
||||
if client_simple_list.is_all {
|
||||
server_node.client_map = map;
|
||||
} else {
|
||||
server_node.client_map.extend(map);
|
||||
}
|
||||
let mut client_simple_map = HashMap::<Ipv4Addr, ClientSimpleInfo>::new();
|
||||
for (_, server_node) in guard.iter() {
|
||||
for (_, x) in server_node.client_map.iter() {
|
||||
if let Some(v) = client_simple_map.get_mut(&x.ip) {
|
||||
if x.online {
|
||||
v.online = true;
|
||||
}
|
||||
} else {
|
||||
client_simple_map.insert(x.ip, x.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut guard = self.client_simple_list.write();
|
||||
*guard = client_simple_map.into_values().collect()
|
||||
}
|
||||
pub fn set_server_connected(&self, server_id: u32, val: bool) -> bool {
|
||||
let mut mutex_guard = self.server_node_map.write();
|
||||
let server_node = mutex_guard.entry(server_id).or_default();
|
||||
let old = server_node.connected;
|
||||
server_node.connected = val;
|
||||
old
|
||||
}
|
||||
pub fn is_any_server_connected(&self, server_ids: Option<&[u32]>) -> bool {
|
||||
let guard = self.server_node_map.read();
|
||||
if let Some(server_ids) = server_ids {
|
||||
for id in server_ids {
|
||||
if guard.get(id).map(|v| v.connected).unwrap_or(false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, server_node) in guard.iter() {
|
||||
if server_node.connected {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
pub fn is_server_connected(&self, server_id: u32) -> bool {
|
||||
self.server_node_map
|
||||
.read()
|
||||
.get(&server_id)
|
||||
.map(|v| v.connected)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn set_last_connected_time(&self, server_id: u32, last_connected_time: Option<i64>) {
|
||||
self.server_node_map
|
||||
.write()
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.last_connected_time = last_connected_time;
|
||||
}
|
||||
pub fn set_disconnected_time(&self, server_id: u32, last_connected_time: Option<i64>) {
|
||||
self.server_node_map
|
||||
.write()
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.disconnected_time = last_connected_time;
|
||||
}
|
||||
pub fn set_server_rtt(&self, server_id: u32, rtt: u32) {
|
||||
if let Some(v) = self.server_node_map.write().get_mut(&server_id) {
|
||||
v.rtt = Some(rtt);
|
||||
}
|
||||
}
|
||||
pub fn set_server_version(&self, server_id: u32, version: String) {
|
||||
if let Some(v) = self.server_node_map.write().get_mut(&server_id) {
|
||||
v.server_version = Some(version);
|
||||
}
|
||||
}
|
||||
pub fn get_server_rtt(&self, ip: &Ipv4Addr) -> Option<u32> {
|
||||
let server_node_map_guard = self.server_node_map.read();
|
||||
for (_, server_node) in server_node_map_guard.iter() {
|
||||
if !server_node.connected {
|
||||
continue;
|
||||
}
|
||||
if let Some(v) = server_node.client_map.get(ip)
|
||||
&& v.online
|
||||
{
|
||||
return server_node.rtt;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn exists_online_client_ip(&self, ip: &Ipv4Addr) -> bool {
|
||||
self.client_simple_list
|
||||
.read()
|
||||
.iter()
|
||||
.any(|v| v.ip == *ip && v.online)
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
self.client_simple_list.write().clear();
|
||||
let mut guard = self.server_node_map.write();
|
||||
for server_node in guard.values_mut() {
|
||||
server_node.connected = false;
|
||||
server_node.rtt = None;
|
||||
server_node.data_version = 0;
|
||||
server_node.client_map.clear();
|
||||
server_node.last_connected_time = None;
|
||||
server_node.disconnected_time = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct NetworkAddr {
|
||||
pub gateway: Ipv4Addr,
|
||||
pub broadcast: Ipv4Addr,
|
||||
pub ip: Ipv4Addr,
|
||||
pub prefix_len: u8,
|
||||
}
|
||||
impl NetworkAddr {
|
||||
pub fn network(&self) -> Ipv4Net {
|
||||
Ipv4Net::new_assert(self.ip, self.prefix_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn stop_network(&self) {
|
||||
self.network.clear();
|
||||
self.server_info_collection.clear();
|
||||
self.peer_map.clear();
|
||||
// route_table 来自外部 crate,会在任务停止后自动失效
|
||||
self.nat_info.clear();
|
||||
self.punch_backoff.clear();
|
||||
self.packet_loss_stats.clear();
|
||||
self.traffic_stats.clear();
|
||||
}
|
||||
|
||||
fn network(&self) -> Option<Ipv4Net> {
|
||||
self.network.network()
|
||||
}
|
||||
pub fn get_network(&self) -> Option<NetworkAddr> {
|
||||
self.network.get()
|
||||
}
|
||||
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
|
||||
let Some(network) = self.network() else {
|
||||
return false;
|
||||
};
|
||||
if network.contains(ip) {
|
||||
return true;
|
||||
}
|
||||
self.subnet_route.route(ip).is_some()
|
||||
}
|
||||
pub fn client_ips(&self) -> Vec<ClientSimpleInfo> {
|
||||
self.server_info_collection.client_ips()
|
||||
}
|
||||
pub fn get_peer_info(&self, ip: &Ipv4Addr) -> Option<PeerClientInfo> {
|
||||
self.peer_map.get(ip)
|
||||
}
|
||||
|
||||
pub fn set_config(&self, config: Box<Config>) {
|
||||
*self.config.lock() = Some(config);
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Option<Box<Config>> {
|
||||
self.config.lock().clone()
|
||||
}
|
||||
pub(crate) fn udp_stun(&self) -> Vec<String> {
|
||||
self.config
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(|v| v.udp_stun.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
pub(crate) fn tcp_stun(&self) -> Vec<String> {
|
||||
self.config
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(|v| v.tcp_stun.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
impl AppState {
|
||||
pub fn get_punch_info(&self) -> Option<PunchInfo> {
|
||||
self.nat_info.get().map(|info| PunchInfo {
|
||||
nat_info: self.filter_ip(info),
|
||||
})
|
||||
}
|
||||
pub fn get_nat_info(&self) -> Option<NatInfo> {
|
||||
self.nat_info.get().map(|info| self.filter_ip(info))
|
||||
}
|
||||
pub fn filter_ip(&self, mut info: NatInfo) -> NatInfo {
|
||||
if self.network_contains(&info.local_ipv4) {
|
||||
info.local_ipv4 = Ipv4Addr::UNSPECIFIED;
|
||||
}
|
||||
info.local_ipv4s.retain(|ip| !self.network_contains(ip));
|
||||
info
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
use parking_lot::RwLock;
|
||||
use rust_p2p_core::nat::NatInfo;
|
||||
use rust_p2p_core::route::Index;
|
||||
use rust_p2p_core::tunnel::udp::UDPIndex;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MyNatInfo {
|
||||
nat_info: Arc<RwLock<Option<NatInfo>>>,
|
||||
}
|
||||
impl MyNatInfo {
|
||||
pub fn get(&self) -> Option<NatInfo> {
|
||||
self.nat_info.read().clone()
|
||||
}
|
||||
pub fn update_public_addr(&self, index: Index, addr: SocketAddr) {
|
||||
let (ip, port) = if let Some(r) = mapping_addr(addr) {
|
||||
r
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
log::debug!("public_addr:{},{},index={index:?}", ip, port);
|
||||
let mut nat_info = self.nat_info.write();
|
||||
let Some(nat_info) = nat_info.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if rust_p2p_core::extend::addr::is_ipv4_global(&ip) {
|
||||
if !nat_info.public_ips.contains(&ip) {
|
||||
nat_info.public_ips.push(ip);
|
||||
}
|
||||
match index {
|
||||
Index::Udp(index) => {
|
||||
let index = match index {
|
||||
UDPIndex::MainV4(index) => index,
|
||||
UDPIndex::MainV6(index) => index,
|
||||
UDPIndex::SubV4(_) => return,
|
||||
};
|
||||
if let Some(p) = nat_info.public_udp_ports.get_mut(index) {
|
||||
*p = port;
|
||||
}
|
||||
}
|
||||
Index::Tcp(_) => {
|
||||
nat_info.public_tcp_port = port;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
log::debug!("not public addr: {addr:?}")
|
||||
}
|
||||
}
|
||||
pub fn update_tcp_public_addr(&self, addr: SocketAddr) {
|
||||
let SocketAddr::V4(addr) = addr else {
|
||||
return;
|
||||
};
|
||||
let ip = *addr.ip();
|
||||
let port = addr.port();
|
||||
log::info!("tcp_public_addr, {}:{}", ip, port);
|
||||
let mut nat_info = self.nat_info.write();
|
||||
let Some(nat_info) = nat_info.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if ip.is_unspecified() && port == 0 {
|
||||
nat_info.public_tcp_port = 0;
|
||||
return;
|
||||
}
|
||||
if rust_p2p_core::extend::addr::is_ipv4_global(&ip) {
|
||||
if !nat_info.public_ips.contains(&ip) {
|
||||
nat_info.public_ips.push(ip);
|
||||
}
|
||||
nat_info.public_tcp_port = port;
|
||||
} else {
|
||||
log::debug!("not public addr: {addr:?}")
|
||||
}
|
||||
}
|
||||
pub fn replace_nat_info(&self, nat_info: NatInfo) {
|
||||
self.nat_info.write().replace(nat_info);
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
*self.nat_info.write() = None;
|
||||
}
|
||||
}
|
||||
fn mapping_addr(addr: SocketAddr) -> Option<(Ipv4Addr, u16)> {
|
||||
match addr {
|
||||
SocketAddr::V4(addr) => Some((*addr.ip(), addr.port())),
|
||||
SocketAddr::V6(addr) => addr.ip().to_ipv4_mapped().map(|ip| (ip, addr.port())),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PunchState {
|
||||
pub count: i64,
|
||||
pub last_ts: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PunchBackoff {
|
||||
inner: Arc<RwLock<HashMap<Ipv4Addr, PunchState>>>,
|
||||
}
|
||||
|
||||
impl PunchBackoff {
|
||||
const MAX_BACKOFF_MS: i64 = 3_600_000; // 1h
|
||||
const BASE_MS: i64 = 3000;
|
||||
|
||||
fn now() -> i64 {
|
||||
crate::utils::time::now_ts_ms()
|
||||
}
|
||||
|
||||
pub fn record(&self, ip: Ipv4Addr) {
|
||||
let mut map = self.inner.write();
|
||||
let entry = map.entry(ip).or_insert(PunchState {
|
||||
count: 0,
|
||||
last_ts: 0,
|
||||
});
|
||||
entry.count += 1;
|
||||
entry.last_ts = Self::now();
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&self, ip: Ipv4Addr) {
|
||||
self.inner.write().remove(&ip);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn get(&self, ip: &Ipv4Addr) -> Option<PunchState> {
|
||||
self.inner.read().get(ip).copied()
|
||||
}
|
||||
|
||||
pub fn should_punch(&self, ip: Ipv4Addr) -> bool {
|
||||
let map = self.inner.read();
|
||||
let Some(state) = map.get(&ip) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let now = Self::now();
|
||||
let elapsed = now - state.last_ts;
|
||||
|
||||
let mut backoff = Self::BASE_MS * state.count;
|
||||
if backoff > Self::MAX_BACKOFF_MS {
|
||||
backoff = Self::MAX_BACKOFF_MS;
|
||||
}
|
||||
|
||||
elapsed >= backoff
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn clear(&self) {
|
||||
self.inner.write().clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
use crate::api::VntApi;
|
||||
use crate::compression::PacketCompression;
|
||||
use crate::context::config::Config;
|
||||
use crate::context::{AppState, NetworkAddr, NetworkRoute};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::enhanced_tunnel::enhanced_ipv4_tunnel;
|
||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
||||
use crate::fec::{FecDecoder, FecEncoder};
|
||||
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
|
||||
use crate::nat::{AllowSubnetExternalRoute, SubnetExternalRoute};
|
||||
use crate::tun::enhanced_tun::EnhancedTunInbound;
|
||||
use crate::tun::{DeviceConfig, DeviceIOManager, TunDataInbound, TunReceiver, tun_channel};
|
||||
use crate::tunnel_core::outbound::{BasicOutbound, HybridOutbound};
|
||||
use crate::tunnel_core::p2p::inbound::{P2pInboundConfig, P2pInboundHandler};
|
||||
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
|
||||
use crate::tunnel_core::p2p::transport::task::init_tunnel;
|
||||
use crate::tunnel_core::server::connection_manager::{
|
||||
InboundHandlerConfig, ServerTurnManager, coordinated_registration, create_server_tunnel,
|
||||
};
|
||||
use crate::tunnel_core::server::rpc::ServerRPC;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::bail;
|
||||
use ipnet::Ipv4Net;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
pub const DEFAULT_MTU: u16 = 1380;
|
||||
|
||||
/// Context for deferred registration
|
||||
struct RegistrationContext {
|
||||
server_managers: Vec<ServerTurnManager>,
|
||||
subnet_external_route: SubnetExternalRoute,
|
||||
puncher: NatPuncher,
|
||||
packet_crypto: PacketCrypto,
|
||||
packet_compression: PacketCompression,
|
||||
enhanced_inbound: EnhancedInbound,
|
||||
fec_decoder: FecDecoder,
|
||||
}
|
||||
|
||||
pub struct NetworkManager {
|
||||
config: Box<Config>,
|
||||
app_state: AppState,
|
||||
task_group: TaskGroup,
|
||||
device_io_manager: DeviceIOManager,
|
||||
enhanced_outbound: Option<EnhancedOutbound>,
|
||||
server_rpc: ServerRPC,
|
||||
tun_receiver: Option<TunReceiver>,
|
||||
registration_context: Option<Box<RegistrationContext>>,
|
||||
}
|
||||
|
||||
impl NetworkManager {
|
||||
pub async fn create_network(
|
||||
config: Box<Config>,
|
||||
task_group: TaskGroup,
|
||||
) -> anyhow::Result<NetworkManager> {
|
||||
let app_state = AppState::default();
|
||||
config.check()?;
|
||||
let mtu = config.mtu.unwrap_or(DEFAULT_MTU);
|
||||
let packet_crypto = PacketCrypto::new_from_str(config.password.as_deref());
|
||||
let packet_compression = PacketCompression::new(config.compress);
|
||||
let (server_manager_list, tunnel_to_server, server_rpc) =
|
||||
create_server_tunnel(app_state.clone(), &config, packet_crypto.clone());
|
||||
let device_io_manager = DeviceIOManager::new(task_group.clone());
|
||||
let allow_subnet = AllowSubnetExternalRoute::new(config.output.clone());
|
||||
|
||||
let (puncher, p2p_socket, p2p_task) = if !config.no_punch {
|
||||
let (puncher, p2p_socket_manager, p2p_task) = init_tunnel(
|
||||
task_group.clone(),
|
||||
app_state.clone(),
|
||||
tunnel_to_server.clone(),
|
||||
packet_crypto.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
(Some(puncher), Some(p2p_socket_manager), Some(p2p_task))
|
||||
} else {
|
||||
(None, None, None)
|
||||
};
|
||||
let puncher = NatPuncher::new(
|
||||
app_state.network.clone(),
|
||||
app_state.punch_backoff.clone(),
|
||||
puncher,
|
||||
packet_crypto.clone(),
|
||||
);
|
||||
let subnet_external_route = app_state.subnet_route.clone();
|
||||
subnet_external_route.set_route_table(config.input.clone());
|
||||
|
||||
let fec_decoder = FecDecoder::new();
|
||||
let basic_outbound = BasicOutbound::new(
|
||||
tunnel_to_server.clone(),
|
||||
p2p_socket.clone(),
|
||||
packet_crypto.clone(),
|
||||
);
|
||||
let fec_encoder = if config.fec {
|
||||
Some(FecEncoder::new(&task_group, basic_outbound.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hybrid_outbound = HybridOutbound::new(
|
||||
app_state.network.clone(),
|
||||
app_state.server_info_collection.clone(),
|
||||
app_state.traffic_stats.clone(),
|
||||
basic_outbound,
|
||||
packet_compression.clone(),
|
||||
subnet_external_route.clone(),
|
||||
fec_encoder,
|
||||
);
|
||||
let port_mapping_manager = PortMappingManager::new(
|
||||
config.no_tun,
|
||||
config.allow_port_mapping,
|
||||
app_state.network.clone(),
|
||||
);
|
||||
let internal_nat_inbound = if config.no_nat && !config.no_tun {
|
||||
None
|
||||
} else {
|
||||
let nat_inbound = InternalNatInbound::create(
|
||||
&task_group,
|
||||
mtu,
|
||||
hybrid_outbound.clone(),
|
||||
allow_subnet.clone(),
|
||||
app_state.network.clone(),
|
||||
config.no_tun,
|
||||
)
|
||||
.await?;
|
||||
Some(nat_inbound)
|
||||
};
|
||||
|
||||
let (enhanced_tun_inbound, tun_receiver) = if config.no_tun {
|
||||
(
|
||||
EnhancedTunInbound::Nat(
|
||||
internal_nat_inbound
|
||||
.clone()
|
||||
.expect("internal_nat_inbound must be Some when no_tun is true"),
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let (tun_inbound, tun_receiver) = tun_channel();
|
||||
let tun_data_sender = TunDataInbound::new(tun_inbound, allow_subnet.clone());
|
||||
(EnhancedTunInbound::Tun(tun_data_sender), Some(tun_receiver))
|
||||
};
|
||||
|
||||
let (enhanced_inbound, enhanced_outbound) = enhanced_ipv4_tunnel(
|
||||
app_state.clone(),
|
||||
task_group.clone(),
|
||||
enhanced_tun_inbound,
|
||||
crate::enhanced_tunnel::TunnelConfig {
|
||||
mtu,
|
||||
password: config.password.clone(),
|
||||
open_quic_client: config.rtx,
|
||||
port_mapping: config.port_mapping.clone(),
|
||||
},
|
||||
crate::enhanced_tunnel::TunnelComponents {
|
||||
hybrid_outbound: hybrid_outbound.clone(),
|
||||
external_route: subnet_external_route.clone(),
|
||||
internal_nat_inbound,
|
||||
port_mapping_manager,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(p2p_task) = p2p_task {
|
||||
let handler = P2pInboundHandler::new(P2pInboundConfig {
|
||||
network_route: NetworkRoute::new(
|
||||
app_state.network.clone(),
|
||||
subnet_external_route.clone(),
|
||||
),
|
||||
route_table: app_state.route_table.clone(),
|
||||
packet_loss_stats: app_state.packet_loss_stats.clone(),
|
||||
packet_crypto: packet_crypto.clone(),
|
||||
packet_compression: packet_compression.clone(),
|
||||
enhanced_inbound: enhanced_inbound.clone(),
|
||||
fec_decoder: fec_decoder.clone(),
|
||||
});
|
||||
p2p_task.start(handler);
|
||||
}
|
||||
|
||||
let registration_context = Box::new(RegistrationContext {
|
||||
server_managers: server_manager_list,
|
||||
subnet_external_route,
|
||||
puncher,
|
||||
packet_crypto,
|
||||
packet_compression,
|
||||
enhanced_inbound,
|
||||
fec_decoder,
|
||||
});
|
||||
|
||||
app_state.set_config(config.clone());
|
||||
Ok(Self {
|
||||
config,
|
||||
app_state,
|
||||
task_group,
|
||||
device_io_manager,
|
||||
enhanced_outbound,
|
||||
server_rpc,
|
||||
tun_receiver,
|
||||
registration_context: Some(registration_context),
|
||||
})
|
||||
}
|
||||
|
||||
/// Register with server(s) and start data handling tasks.
|
||||
/// This method can only be called once.
|
||||
/// Returns the registration response on success.
|
||||
pub async fn register(&mut self) -> anyhow::Result<NetworkAddr> {
|
||||
let Some(mut ctx) = self.registration_context.take() else {
|
||||
bail!("register can only be called once");
|
||||
};
|
||||
|
||||
let is_multi_server = ctx.server_managers.len() > 1;
|
||||
|
||||
let reg_response = if is_multi_server {
|
||||
// Multi-server: coordinated pre-registration
|
||||
log::info!(
|
||||
"Multi-server mode: performing coordinated registration for {} servers",
|
||||
ctx.server_managers.len()
|
||||
);
|
||||
let reg_response = coordinated_registration(&mut ctx.server_managers).await?;
|
||||
log::info!(
|
||||
"Coordinated registration completed, IP: {}, prefix_len: {}",
|
||||
reg_response.ip,
|
||||
reg_response.prefix_len
|
||||
);
|
||||
reg_response
|
||||
} else {
|
||||
// Single-server: normal registration
|
||||
log::info!("Single-server mode: performing normal registration");
|
||||
let response = ctx.server_managers[0]
|
||||
.connect_and_reg(crate::protocol::control_message::RegistrationMode::Normal)
|
||||
.await?;
|
||||
match response {
|
||||
crate::protocol::control_message::ResponseMessage::Reg(reg) => {
|
||||
log::info!(
|
||||
"Registration completed, IP: {}, prefix_len: {}",
|
||||
reg.ip,
|
||||
reg.prefix_len
|
||||
);
|
||||
reg
|
||||
}
|
||||
crate::protocol::control_message::ResponseMessage::Error(e) => {
|
||||
bail!("Registration failed: {}", e.message);
|
||||
}
|
||||
crate::protocol::control_message::ResponseMessage::ConfirmReg(_) => {
|
||||
bail!("Unexpected ConfirmReg response");
|
||||
}
|
||||
}
|
||||
};
|
||||
let network_addr = NetworkAddr {
|
||||
gateway: reg_response.gateway,
|
||||
broadcast: Ipv4Net::new(reg_response.ip, reg_response.prefix_len)?.broadcast(),
|
||||
ip: reg_response.ip,
|
||||
prefix_len: reg_response.prefix_len,
|
||||
};
|
||||
self.app_state.network.set(network_addr);
|
||||
|
||||
// 保存服务器版本信息
|
||||
if !reg_response.server_version.is_empty() {
|
||||
for (index, _) in ctx.server_managers.iter().enumerate() {
|
||||
self.app_state.server_info_collection.set_server_version(
|
||||
index as u32,
|
||||
reg_response.server_version.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Start data handling tasks for all servers
|
||||
for turn_manager in ctx.server_managers {
|
||||
let handler_config = Box::new(InboundHandlerConfig {
|
||||
network_route: NetworkRoute::new(
|
||||
self.app_state.network.clone(),
|
||||
ctx.subnet_external_route.clone(),
|
||||
),
|
||||
server_info: self.app_state.server_info_collection.clone(),
|
||||
nat_info: self.app_state.nat_info.clone(),
|
||||
peer_map: self.app_state.peer_map.clone(),
|
||||
punch_backoff: self.app_state.punch_backoff.clone(),
|
||||
puncher: ctx.puncher.clone(),
|
||||
packet_crypto: ctx.packet_crypto.clone(),
|
||||
packet_compression: ctx.packet_compression.clone(),
|
||||
enhanced_inbound: ctx.enhanced_inbound.clone(),
|
||||
fec_decoder: ctx.fec_decoder.clone(),
|
||||
});
|
||||
turn_manager.data_handle_task_connected(&self.task_group, handler_config, network_addr);
|
||||
}
|
||||
|
||||
Ok(network_addr)
|
||||
}
|
||||
|
||||
pub fn is_no_tun(&self) -> bool {
|
||||
self.config.no_tun
|
||||
}
|
||||
|
||||
pub async fn start_tun(&mut self) -> anyhow::Result<()> {
|
||||
let Some(receiver) = self.tun_receiver.take() else {
|
||||
bail!("start_tun can only be called once");
|
||||
};
|
||||
let Some(enhanced_outbound) = self.enhanced_outbound.take() else {
|
||||
bail!("start_tun can only be called once");
|
||||
};
|
||||
let mut config = DeviceConfig::default();
|
||||
config = config.set_mtu(self.config.mtu.unwrap_or(DEFAULT_MTU));
|
||||
if let Some(tun_name) = self.config.tun_name.clone() {
|
||||
config = config.set_tun_name(tun_name);
|
||||
}
|
||||
self.device_io_manager
|
||||
.start_task(config, receiver, enhanced_outbound)
|
||||
.await
|
||||
}
|
||||
#[cfg(unix)]
|
||||
pub async fn start_tun_fd(&mut self, tun_fd: Option<i32>) -> anyhow::Result<()> {
|
||||
let Some(receiver) = self.tun_receiver.take() else {
|
||||
bail!("start_tun_fd can only be called once");
|
||||
};
|
||||
let Some(enhanced_outbound) = self.enhanced_outbound.take() else {
|
||||
bail!("start_tun_fd can only be called once");
|
||||
};
|
||||
let mut config = DeviceConfig::default();
|
||||
if let Some(tun_fd) = tun_fd {
|
||||
config = config.set_tun_fd(tun_fd);
|
||||
}
|
||||
if let Some(tun_name) = self.config.tun_name.clone() {
|
||||
config = config.set_tun_name(tun_name);
|
||||
}
|
||||
self.device_io_manager
|
||||
.start_task(config, receiver, enhanced_outbound)
|
||||
.await
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub async fn set_network_ip(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
||||
self.device_io_manager.set_network(ip, prefix_len).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop_network(&mut self) {
|
||||
self.task_group.stop();
|
||||
self.app_state.stop_network();
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
|
||||
self.device_io_manager.tun_if_index().await
|
||||
}
|
||||
pub async fn wait_all_stopped(&mut self) {
|
||||
self.task_group.wait_all_stopped().await;
|
||||
}
|
||||
pub fn vnt_api(&self) -> VntApi {
|
||||
VntApi::new(self.app_state.clone(), self.server_rpc.clone())
|
||||
}
|
||||
}
|
||||
impl Drop for NetworkManager {
|
||||
fn drop(&mut self) {
|
||||
self.stop_network();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
|
||||
use ring::aead::{Aad, CHACHA20_POLY1305, LessSafeKey, Nonce, UnboundKey};
|
||||
use std::io;
|
||||
|
||||
pub const TAG_LEN: usize = 16;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketCrypto {
|
||||
key: LessSafeKey,
|
||||
}
|
||||
|
||||
impl PacketCrypto {
|
||||
pub fn key_sign(s: &str) -> String {
|
||||
use ring::digest::{Context, SHA256};
|
||||
|
||||
const PREFIX: &[u8] = b"KEY-BEGIN";
|
||||
const SUFFIX: &[u8] = b"KEY-END";
|
||||
|
||||
let mut ctx = Context::new(&SHA256);
|
||||
ctx.update(PREFIX);
|
||||
ctx.update(s.as_bytes());
|
||||
ctx.update(SUFFIX);
|
||||
let digest = ctx.finish();
|
||||
let mut key_bytes = [0u8; 16];
|
||||
key_bytes.copy_from_slice(&digest.as_ref()[..16]);
|
||||
key_bytes
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<String>()
|
||||
}
|
||||
pub fn new(key_bytes: [u8; 32]) -> Self {
|
||||
let unbound = UnboundKey::new(&CHACHA20_POLY1305, &key_bytes).unwrap();
|
||||
let key = LessSafeKey::new(unbound);
|
||||
Self { key }
|
||||
}
|
||||
pub fn new_from_str(s: &str) -> Self {
|
||||
let hash = ring::digest::digest(&ring::digest::SHA256, s.as_bytes());
|
||||
let mut key_bytes = [0u8; 32];
|
||||
key_bytes.copy_from_slice(hash.as_ref());
|
||||
Self::new(key_bytes)
|
||||
}
|
||||
/// 根据包头生成 12 字节 nonce
|
||||
pub fn make_nonce<B: AsRef<[u8]>>(&self, pkt: &NetPacket<B>) -> io::Result<[u8; 12]> {
|
||||
let buf = pkt.buffer();
|
||||
|
||||
if buf.len() < HEAD_LENGTH {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"buffer too small",
|
||||
));
|
||||
}
|
||||
let msg_type = buf[0];
|
||||
let seq = &buf[4..8];
|
||||
let src = &buf[8..12];
|
||||
let dst = &buf[12..16];
|
||||
|
||||
let mut nonce12 = [0u8; 12];
|
||||
nonce12[0..4].copy_from_slice(seq);
|
||||
nonce12[4..8].copy_from_slice(dst);
|
||||
nonce12[8..12].copy_from_slice(src);
|
||||
nonce12[0] = msg_type;
|
||||
|
||||
Ok(nonce12)
|
||||
}
|
||||
|
||||
/// 原地加密(in-place)
|
||||
/// payload 后需要预留16字节用于存放 tag
|
||||
pub fn encrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]>>(
|
||||
&self,
|
||||
pkt: &mut NetPacket<B>,
|
||||
) -> io::Result<()> {
|
||||
let nonce = Nonce::assume_unique_for_key(self.make_nonce(pkt)?);
|
||||
|
||||
let payload = pkt.payload_mut();
|
||||
let payload_len = payload.len() - TAG_LEN; // 实际 payload 长度(不含 tag 预留空间)
|
||||
|
||||
// 只加密实际的 payload 部分
|
||||
let tag = self
|
||||
.key
|
||||
.seal_in_place_separate_tag(nonce, Aad::empty(), &mut payload[..payload_len])
|
||||
.map_err(|_| io::Error::other("encrypt failed"))?;
|
||||
|
||||
// 将 tag 写入 payload 后的预留空间
|
||||
payload[payload_len..payload_len + TAG_LEN].copy_from_slice(tag.as_ref());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 原地解密(in-place)
|
||||
pub fn decrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]>>(
|
||||
&self,
|
||||
pkt: &mut NetPacket<B>,
|
||||
) -> io::Result<usize> {
|
||||
let nonce = Nonce::assume_unique_for_key(self.make_nonce(pkt)?);
|
||||
|
||||
let payload_with_tag = pkt.payload_mut();
|
||||
|
||||
let plaintext = self
|
||||
.key
|
||||
.open_in_place(nonce, Aad::empty(), payload_with_tag)
|
||||
.map_err(|_| io::Error::other("decrypt failed"))?;
|
||||
Ok(plaintext.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::BytesMut;
|
||||
|
||||
// 用于构造一个简单的 NetPacket,包含头 16 字节 + payload + 16 字节 TAG 预留
|
||||
fn build_test_packet(payload_len: usize) -> NetPacket<BytesMut> {
|
||||
// 16 字节 head + payload + 16 字节预留 TAG
|
||||
let total_len = HEAD_LENGTH + payload_len + TAG_LEN;
|
||||
let mut buf = BytesMut::zeroed(total_len);
|
||||
|
||||
// 构造一个头(16 字节)
|
||||
buf[0] = 4; // MsgType::Ping
|
||||
buf[4..8].copy_from_slice(&12345u32.to_be_bytes());
|
||||
buf[8..12].copy_from_slice(&111u32.to_be_bytes());
|
||||
buf[12..16].copy_from_slice(&222u32.to_be_bytes());
|
||||
|
||||
// 构造 payload(明文)
|
||||
let payload_plain = &mut buf[HEAD_LENGTH..HEAD_LENGTH + payload_len];
|
||||
for (i, p) in payload_plain.iter_mut().enumerate() {
|
||||
*p = (i as u8) ^ 0xAB;
|
||||
}
|
||||
|
||||
NetPacket::new(buf).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_in_place() {
|
||||
let key = [7u8; 32];
|
||||
let crypto = PacketCrypto::new(key);
|
||||
|
||||
let payload_len = 20;
|
||||
let mut pkt = build_test_packet(payload_len);
|
||||
|
||||
// 备份原 payload
|
||||
let original_payload: Vec<u8> =
|
||||
pkt.buffer()[HEAD_LENGTH..HEAD_LENGTH + payload_len].to_vec();
|
||||
|
||||
// 加密
|
||||
crypto.encrypt_in_place(&mut pkt).expect("encrypt failed");
|
||||
|
||||
let encrypted_buf = pkt.buffer();
|
||||
let tag_start = HEAD_LENGTH + payload_len;
|
||||
let tag_end = tag_start + TAG_LEN;
|
||||
|
||||
// TAG 不应该是全 0
|
||||
assert_ne!(&encrypted_buf[tag_start..tag_end], &[0u8; TAG_LEN]);
|
||||
|
||||
// payload 已被加密,不等于明文
|
||||
assert_ne!(
|
||||
&encrypted_buf[HEAD_LENGTH..HEAD_LENGTH + payload_len],
|
||||
&original_payload[..]
|
||||
);
|
||||
|
||||
// 解密
|
||||
crypto.decrypt_in_place(&mut pkt).expect("decrypt failed");
|
||||
|
||||
let decrypted_buf = pkt.buffer();
|
||||
let decrypted_payload = &decrypted_buf[HEAD_LENGTH..HEAD_LENGTH + payload_len];
|
||||
|
||||
// 解密后与原文一致
|
||||
assert_eq!(decrypted_payload, &original_payload[..]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use crate::crypto::chacha20_poly1305::TAG_LEN;
|
||||
use crate::protocol::ip_packet_protocol::NetPacket;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod chacha20_poly1305;
|
||||
|
||||
use crate::protocol::transmission::{ExtendEnd, ShrinkEnd};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketCrypto {
|
||||
crypto: Option<Arc<chacha20_poly1305::PacketCrypto>>,
|
||||
}
|
||||
impl PacketCrypto {
|
||||
pub(crate) fn key_sign(s: &str) -> String {
|
||||
chacha20_poly1305::PacketCrypto::key_sign(s)
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_str(s: Option<&str>) -> Self {
|
||||
Self {
|
||||
crypto: s.map(chacha20_poly1305::PacketCrypto::new_from_str).map(Arc::new),
|
||||
}
|
||||
}
|
||||
pub(crate) fn encrypt_reserve(&self) -> usize {
|
||||
if self.crypto.is_some() { TAG_LEN } else { 0 }
|
||||
}
|
||||
pub(crate) fn encrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]> + ExtendEnd>(
|
||||
&self,
|
||||
pkt: &mut NetPacket<B>,
|
||||
) -> io::Result<()> {
|
||||
if let Some(crypto) = self.crypto.as_ref() {
|
||||
pkt.source_buf_mut().extend_end(TAG_LEN);
|
||||
return crypto.encrypt_in_place(pkt);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) fn decrypt_in_place<B: AsRef<[u8]> + AsMut<[u8]> + ShrinkEnd>(
|
||||
&self,
|
||||
pkt: &mut NetPacket<B>,
|
||||
) -> io::Result<()> {
|
||||
if let Some(crypto) = self.crypto.as_ref() {
|
||||
let _ = crypto.decrypt_in_place(pkt)?;
|
||||
pkt.source_buf_mut().shrink_end(TAG_LEN);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use crate::context::{NetworkAddr, TrafficStats};
|
||||
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
|
||||
use crate::nat::internal_nat::InternalNatInbound;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tun::enhanced_tun::EnhancedTunInbound;
|
||||
use anyhow::{Context, bail};
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct EnhancedInbound {
|
||||
tun_data_inbound: EnhancedTunInbound,
|
||||
quic_inbound: EnhancedQuicInbound,
|
||||
internal_nat_inbound: Option<InternalNatInbound>,
|
||||
traffic_stats: TrafficStats,
|
||||
}
|
||||
|
||||
impl EnhancedInbound {
|
||||
pub fn new(
|
||||
tun_data_inbound: EnhancedTunInbound,
|
||||
quic_inbound: EnhancedQuicInbound,
|
||||
internal_nat_inbound: Option<InternalNatInbound>,
|
||||
traffic_stats: TrafficStats,
|
||||
) -> Self {
|
||||
Self {
|
||||
tun_data_inbound,
|
||||
quic_inbound,
|
||||
internal_nat_inbound,
|
||||
traffic_stats,
|
||||
}
|
||||
}
|
||||
pub async fn inbound(
|
||||
&self,
|
||||
network_addr: &NetworkAddr,
|
||||
msg_type: MsgType,
|
||||
src: Ipv4Addr,
|
||||
packet: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut buf = packet.into_buffer();
|
||||
self.traffic_stats.record_rx(src, buf.len() as u64);
|
||||
buf.advance_head(HEAD_LENGTH)?;
|
||||
|
||||
match msg_type {
|
||||
MsgType::Turn => {
|
||||
if let Some(internal_nat_inbound) = self.internal_nat_inbound.as_ref() {
|
||||
let Some(ipv4) = Ipv4Packet::new(&buf) else {
|
||||
bail!("EnhancedInbound not ipv4")
|
||||
};
|
||||
let dest = ipv4.get_destination();
|
||||
if dest != network_addr.ip && !network_addr.network().contains(&dest) {
|
||||
internal_nat_inbound.send(&buf, network_addr).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.tun_data_inbound.inbound(buf, network_addr).await?;
|
||||
}
|
||||
MsgType::Broadcast | MsgType::ExcludeBroadcast => {
|
||||
self.tun_data_inbound.inbound(buf, network_addr).await?;
|
||||
}
|
||||
MsgType::Quic => {
|
||||
let payload = buf.into_bytes().freeze();
|
||||
self.quic_inbound
|
||||
.inbound(payload, src)
|
||||
.await
|
||||
.context("inbound quic")?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::context::AppState;
|
||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
||||
use crate::nat::SubnetExternalRoute;
|
||||
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
|
||||
use crate::port_mapping::PortMapping;
|
||||
use crate::tun::enhanced_tun::EnhancedTunInbound;
|
||||
use crate::tunnel_core::outbound::HybridOutbound;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
|
||||
pub(crate) mod quic_over;
|
||||
|
||||
pub(crate) mod inbound;
|
||||
pub(crate) mod outbound;
|
||||
|
||||
pub(crate) struct TunnelConfig {
|
||||
pub mtu: u16,
|
||||
pub password: Option<String>,
|
||||
pub open_quic_client: bool,
|
||||
pub port_mapping: Vec<PortMapping>,
|
||||
}
|
||||
|
||||
pub(crate) struct TunnelComponents {
|
||||
pub hybrid_outbound: HybridOutbound,
|
||||
pub external_route: SubnetExternalRoute,
|
||||
pub internal_nat_inbound: Option<InternalNatInbound>,
|
||||
pub port_mapping_manager: PortMappingManager,
|
||||
}
|
||||
|
||||
pub(crate) async fn enhanced_ipv4_tunnel(
|
||||
app_state: AppState,
|
||||
task_group: TaskGroup,
|
||||
tun_data_sender: EnhancedTunInbound,
|
||||
config: TunnelConfig,
|
||||
components: TunnelComponents,
|
||||
) -> anyhow::Result<(EnhancedInbound, Option<EnhancedOutbound>)> {
|
||||
let password = config.password.unwrap_or_else(|| "password".to_string());
|
||||
let tun = match &tun_data_sender {
|
||||
EnhancedTunInbound::Tun(tun) => Some(tun.clone()),
|
||||
EnhancedTunInbound::Nat(_) => None,
|
||||
};
|
||||
let (inbound, outbound) = quic_over::boot::quic_tunnel_start(
|
||||
app_state.clone(),
|
||||
task_group,
|
||||
tun,
|
||||
quic_over::boot::QuicTunnelConfig {
|
||||
mtu: config.mtu,
|
||||
password,
|
||||
open_quic_client: config.open_quic_client,
|
||||
port_mapping: config.port_mapping,
|
||||
},
|
||||
quic_over::boot::QuicTunnelComponents {
|
||||
hybrid_outbound: components.hybrid_outbound.clone(),
|
||||
external_route: components.external_route,
|
||||
internal_nat_manager: components.internal_nat_inbound.clone(),
|
||||
port_mapping_manager: components.port_mapping_manager,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let enhanced_inbound = EnhancedInbound::new(
|
||||
tun_data_sender,
|
||||
inbound,
|
||||
components.internal_nat_inbound,
|
||||
app_state.traffic_stats.clone(),
|
||||
);
|
||||
|
||||
let enhanced_outbound = outbound.map(|outbound| {
|
||||
EnhancedOutbound::new(
|
||||
app_state.network.clone(),
|
||||
outbound,
|
||||
components.hybrid_outbound,
|
||||
)
|
||||
});
|
||||
Ok((enhanced_inbound, enhanced_outbound))
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::context::SharedNetworkAddr;
|
||||
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::outbound::HybridOutbound;
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
|
||||
pub struct EnhancedOutbound {
|
||||
network: SharedNetworkAddr,
|
||||
enhanced_quic_outbound: EnhancedQuicOutbound,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
}
|
||||
|
||||
impl EnhancedOutbound {
|
||||
pub fn new(
|
||||
network: SharedNetworkAddr,
|
||||
enhanced_quic_outbound: EnhancedQuicOutbound,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
) -> Self {
|
||||
Self {
|
||||
network,
|
||||
enhanced_quic_outbound,
|
||||
hybrid_outbound,
|
||||
}
|
||||
}
|
||||
pub async fn ipv4_outbound(&self, data: TransmissionBytes) {
|
||||
if data.is_empty() || data[0] >> 4 != 4 {
|
||||
return;
|
||||
}
|
||||
if let Err(e) = self.ipv4_outbound_impl(data).await {
|
||||
log::warn!("EnhancedOutbound error: {:?}", e);
|
||||
}
|
||||
}
|
||||
async fn ipv4_outbound_impl(&self, data: TransmissionBytes) -> anyhow::Result<()> {
|
||||
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(net) = self.network.get() else {
|
||||
return Ok(());
|
||||
};
|
||||
let src = ipv4.get_source();
|
||||
|
||||
let dest = ipv4.get_destination();
|
||||
if dest == src || dest.is_unspecified() {
|
||||
return Ok(());
|
||||
}
|
||||
if dest == net.gateway {
|
||||
// 发送到网关
|
||||
return self.hybrid_outbound.ipv4_gateway_outbound(net, data).await;
|
||||
}
|
||||
if dest.is_multicast() || dest == net.broadcast || dest.is_broadcast() {
|
||||
// 广播
|
||||
return self
|
||||
.hybrid_outbound
|
||||
.ipv4_broadcast_outbound(net, data)
|
||||
.await;
|
||||
}
|
||||
if self
|
||||
.enhanced_quic_outbound
|
||||
.outbound(&net, data.as_ref())
|
||||
.await
|
||||
{
|
||||
// 使用quic 通道传输
|
||||
return Ok(());
|
||||
}
|
||||
// 使用通用通道传输
|
||||
self.hybrid_outbound.ipv4_outbound(net, data).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
use crate::context::AppState;
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::{
|
||||
QuicDataInbound, create_enhanced_inbound,
|
||||
};
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_outbound::create_enhanced_outbound;
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::socket::ExtendedQuicSocket;
|
||||
use crate::enhanced_tunnel::quic_over::quic_client::QuicTunnelClient;
|
||||
use crate::enhanced_tunnel::quic_over::quic_inbound::EnhancedQuicInbound;
|
||||
use crate::enhanced_tunnel::quic_over::quic_outbound::EnhancedQuicOutbound;
|
||||
use crate::enhanced_tunnel::quic_over::{quic_client, quic_server};
|
||||
use crate::nat::SubnetExternalRoute;
|
||||
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
|
||||
use crate::port_mapping::PortMapping;
|
||||
use crate::tls;
|
||||
use crate::tun::TunDataInbound;
|
||||
use crate::tunnel_core::outbound::HybridOutbound;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use quinn::congestion::BbrConfig;
|
||||
use quinn::crypto::rustls::QuicServerConfig;
|
||||
use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig, default_runtime};
|
||||
use rustls::ServerConfig;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tcp_ip::{IpStackConfig, IpStackRecv};
|
||||
|
||||
pub(crate) struct QuicTunnelConfig {
|
||||
pub mtu: u16,
|
||||
pub password: String,
|
||||
pub open_quic_client: bool,
|
||||
pub port_mapping: Vec<PortMapping>,
|
||||
}
|
||||
|
||||
pub(crate) struct QuicTunnelComponents {
|
||||
pub hybrid_outbound: HybridOutbound,
|
||||
pub external_route: SubnetExternalRoute,
|
||||
pub internal_nat_manager: Option<InternalNatInbound>,
|
||||
pub port_mapping_manager: PortMappingManager,
|
||||
}
|
||||
|
||||
pub(crate) async fn quic_tunnel_start(
|
||||
app_state: AppState,
|
||||
task_group: TaskGroup,
|
||||
tun_data_sender: Option<TunDataInbound>,
|
||||
config: QuicTunnelConfig,
|
||||
components: QuicTunnelComponents,
|
||||
) -> anyhow::Result<(EnhancedQuicInbound, Option<EnhancedQuicOutbound>)> {
|
||||
let ip_stack_config = IpStackConfig {
|
||||
mtu: config.mtu,
|
||||
..Default::default()
|
||||
};
|
||||
let (ip_stack, ip_socket, quic_outbound) = if let Some(tun_data_sender) = tun_data_sender {
|
||||
let (ip_stack, ip_stack_send, ip_stack_recv) = tcp_ip::ip_stack(ip_stack_config)?;
|
||||
let ip_socket = tcp_ip::ip::IpSocket::bind_all(None, ip_stack.clone()).await?;
|
||||
let ip_socket = Arc::new(ip_socket);
|
||||
task_group.spawn(ip_stack_recv_task(
|
||||
ip_stack_recv,
|
||||
app_state.clone(),
|
||||
tun_data_sender,
|
||||
));
|
||||
let quic_outbound =
|
||||
EnhancedQuicOutbound::new(config.open_quic_client, ip_stack_send, ip_stack.clone());
|
||||
|
||||
(Some(ip_stack), Some(ip_socket), Some(quic_outbound))
|
||||
} else {
|
||||
(None, None, None)
|
||||
};
|
||||
|
||||
let (inbound, endpoint) = create_quic_endpoint(
|
||||
config.password,
|
||||
task_group.clone(),
|
||||
components.hybrid_outbound,
|
||||
)
|
||||
.await?;
|
||||
quic_server::server_listen(
|
||||
&task_group,
|
||||
endpoint.clone(),
|
||||
ip_socket.clone(),
|
||||
ip_stack.clone(),
|
||||
components.internal_nat_manager,
|
||||
components.port_mapping_manager,
|
||||
)
|
||||
.await;
|
||||
if config.open_quic_client {
|
||||
let quic_client =
|
||||
QuicTunnelClient::new(app_state.clone(), endpoint, components.external_route);
|
||||
|
||||
// 客户端使用指纹验证
|
||||
if let (Some(ip_stack), Some(ip_socket)) = (ip_stack, ip_socket) {
|
||||
quic_client::create_client(
|
||||
quic_client.clone(),
|
||||
task_group.clone(),
|
||||
ip_stack.clone(),
|
||||
ip_socket,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if !config.port_mapping.is_empty() {
|
||||
crate::port_mapping::port_mapping_start(&task_group, config.port_mapping, quic_client)
|
||||
.await?;
|
||||
}
|
||||
} else if !config.port_mapping.is_empty() {
|
||||
let quic_client =
|
||||
QuicTunnelClient::new(app_state.clone(), endpoint, components.external_route);
|
||||
|
||||
crate::port_mapping::port_mapping_start(&task_group, config.port_mapping, quic_client)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let quic_inbound = EnhancedQuicInbound::new(inbound);
|
||||
Ok((quic_inbound, quic_outbound))
|
||||
}
|
||||
async fn create_quic_endpoint(
|
||||
password: String,
|
||||
task_group: TaskGroup,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
) -> anyhow::Result<(QuicDataInbound, Endpoint)> {
|
||||
let (cert, private_key) = crate::tls::cert::generate_deterministic_cert(&password)?;
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(cert.as_ref());
|
||||
let calculated_hash: [u8; 32] = hasher.finalize().into();
|
||||
log::info!("QUIC Cert Fingerprint: {}", hex::encode(calculated_hash));
|
||||
|
||||
let outbound = create_enhanced_outbound(task_group.clone(), hybrid_outbound).await;
|
||||
let (inbound, inbound_receiver) = create_enhanced_inbound();
|
||||
let socket = ExtendedQuicSocket::new(inbound_receiver, outbound);
|
||||
|
||||
let server_config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(vec![cert], private_key)
|
||||
.context("TLS config error")?;
|
||||
|
||||
let server_crypto = QuicServerConfig::try_from(server_config)
|
||||
.map_err(|e| anyhow::anyhow!("QUIC TLS config error: {:?}", e))?;
|
||||
let server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto));
|
||||
// 替换运行时
|
||||
let runtime = default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?;
|
||||
let fingerprint_verifier = tls::verifier::FingerprintVerifier::new(calculated_hash);
|
||||
|
||||
let client_config = rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(fingerprint_verifier))
|
||||
.with_no_client_auth();
|
||||
let mut client_config = ClientConfig::new(Arc::new(
|
||||
quinn::crypto::rustls::QuicClientConfig::try_from(client_config)
|
||||
.context("Failed to create QUIC client config")?,
|
||||
));
|
||||
client_config.transport_config(build_transport_config());
|
||||
let mut endpoint_config = EndpointConfig::default();
|
||||
endpoint_config.max_udp_payload_size(1300)?;
|
||||
let mut endpoint = quinn::Endpoint::new_with_abstract_socket(
|
||||
endpoint_config,
|
||||
Some(server_config),
|
||||
Arc::new(socket),
|
||||
runtime,
|
||||
)
|
||||
.context("quic server create failed")?;
|
||||
endpoint.set_default_client_config(client_config);
|
||||
Ok((inbound, endpoint))
|
||||
}
|
||||
|
||||
fn build_transport_config() -> Arc<TransportConfig> {
|
||||
let mut transport = TransportConfig::default();
|
||||
transport.congestion_controller_factory(Arc::new(BbrConfig::default()));
|
||||
transport.keep_alive_interval(Some(Duration::from_secs(5)));
|
||||
|
||||
transport.max_idle_timeout(Some(Duration::from_secs(10).try_into().unwrap()));
|
||||
|
||||
Arc::new(transport)
|
||||
}
|
||||
|
||||
async fn ip_stack_recv_task(
|
||||
mut ip_stack_recv: IpStackRecv,
|
||||
app_state: AppState,
|
||||
tun_data_sender: TunDataInbound,
|
||||
) {
|
||||
let mut buf = vec![0u8; 1500];
|
||||
loop {
|
||||
let len = match ip_stack_recv.recv(&mut buf).await {
|
||||
Ok(len) => len,
|
||||
Err(e) => {
|
||||
log::error!("IP stack recv error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let Some(net) = app_state.get_network() else {
|
||||
log::error!("not network");
|
||||
break;
|
||||
};
|
||||
match tun_data_sender.send((&buf[..len]).into(), &net).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
log::error!("IP stack send error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use anyhow::anyhow;
|
||||
use bytes::Bytes;
|
||||
use parking_lot::Mutex;
|
||||
use quinn::udp::RecvMeta;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::io::IoSliceMut;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QuicInnerInboundReceiver {
|
||||
receiver: Arc<Mutex<Receiver<(Bytes, Ipv4Addr)>>>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct QuicDataInbound {
|
||||
sender: Sender<(Bytes, Ipv4Addr)>,
|
||||
}
|
||||
impl QuicDataInbound {
|
||||
pub async fn send(&self, data: Bytes, addr: Ipv4Addr) -> anyhow::Result<()> {
|
||||
self.sender
|
||||
.send((data, addr))
|
||||
.await
|
||||
.map_err(|_e| anyhow!("quic data inbound error"))
|
||||
}
|
||||
}
|
||||
impl Debug for QuicInnerInboundReceiver {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EnhancedInbound").finish()
|
||||
}
|
||||
}
|
||||
pub fn create_enhanced_inbound() -> (QuicDataInbound, QuicInnerInboundReceiver) {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel(256);
|
||||
(
|
||||
QuicDataInbound { sender },
|
||||
QuicInnerInboundReceiver::new(receiver),
|
||||
)
|
||||
}
|
||||
impl QuicInnerInboundReceiver {
|
||||
pub fn new(receiver: Receiver<(Bytes, Ipv4Addr)>) -> Self {
|
||||
Self {
|
||||
receiver: Arc::new(Mutex::new(receiver)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_recv(
|
||||
&self,
|
||||
cx: &mut Context,
|
||||
bufs: &mut [IoSliceMut<'_>],
|
||||
meta: &mut [RecvMeta],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
let mut guard = self.receiver.lock();
|
||||
let rs = guard.poll_recv(cx);
|
||||
drop(guard);
|
||||
match rs {
|
||||
Poll::Ready(Some((buf, ip))) => {
|
||||
let (buf_mut, meta) = match (bufs.get_mut(0), meta.get_mut(0)) {
|
||||
(Some(b), Some(m)) => (b, m),
|
||||
_ => {
|
||||
return Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"no buffer available",
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if buf_mut.len() < buf.len() {
|
||||
return Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"buffer too small: need {}, got {}",
|
||||
buf.len(),
|
||||
buf_mut.len()
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
buf_mut[..buf.len()].copy_from_slice(&buf);
|
||||
|
||||
meta.len = buf.len();
|
||||
meta.stride = buf.len();
|
||||
meta.addr = SocketAddr::V4(SocketAddrV4::new(ip, 10000));
|
||||
Poll::Ready(Ok(1))
|
||||
}
|
||||
Poll::Ready(None) => Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::BrokenPipe,
|
||||
"inbound channel closed",
|
||||
))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::outbound::HybridOutbound;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use quinn::UdpPoller;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::io;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::mpsc::{Sender, error::TrySendError};
|
||||
use tokio_util::sync::PollSender;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QuicInnerOutbound {
|
||||
sender: Sender<(Ipv4Addr, NetPacket<TransmissionBytes>)>,
|
||||
}
|
||||
|
||||
pub async fn create_enhanced_outbound(
|
||||
task_group: TaskGroup,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
) -> QuicInnerOutbound {
|
||||
let (s, mut r) = tokio::sync::mpsc::channel(256);
|
||||
|
||||
task_group.spawn(async move {
|
||||
while let Some((dst, packet)) = r.recv().await {
|
||||
if let Err(e) = hybrid_outbound.outbound_raw(dst, packet).await {
|
||||
log::debug!("outbound error: {e:?}, dst={dst}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QuicInnerOutbound { sender: s }
|
||||
}
|
||||
|
||||
impl QuicInnerOutbound {
|
||||
pub fn try_outbound(&self, buf: &[u8], dest: Ipv4Addr) -> io::Result<()> {
|
||||
let send = match self.sender.try_reserve() {
|
||||
Ok(send) => send,
|
||||
Err(TrySendError::Full(_)) => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"outbound channel full",
|
||||
));
|
||||
}
|
||||
Err(TrySendError::Closed(_)) => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"outbound channel closed",
|
||||
));
|
||||
}
|
||||
};
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + buf.len()))?;
|
||||
packet.set_ttl(5);
|
||||
packet.set_msg_type(MsgType::Quic);
|
||||
packet.set_dest_id(dest.into());
|
||||
packet.set_payload(buf)?;
|
||||
send.send((dest, packet));
|
||||
Ok(())
|
||||
}
|
||||
pub fn create_io_poller(&self) -> Pin<Box<dyn UdpPoller>> {
|
||||
Box::pin(EnhancedOutboundPoller {
|
||||
sender: PollSender::new(self.sender.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub struct EnhancedOutboundPoller {
|
||||
sender: PollSender<(Ipv4Addr, NetPacket<TransmissionBytes>)>,
|
||||
}
|
||||
impl Debug for EnhancedOutboundPoller {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EnhancedOutboundPoller").finish()
|
||||
}
|
||||
}
|
||||
impl UdpPoller for EnhancedOutboundPoller {
|
||||
fn poll_writable(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
|
||||
match self.sender.poll_reserve(cx) {
|
||||
Poll::Ready(Ok(_)) => {
|
||||
self.sender.abort_send();
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
Poll::Ready(Err(_e)) => Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"outbound channel closed",
|
||||
))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod enhanced_inbound;
|
||||
pub mod enhanced_outbound;
|
||||
pub mod socket;
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::QuicInnerInboundReceiver;
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_outbound::QuicInnerOutbound;
|
||||
use quinn::udp::{RecvMeta, Transmit};
|
||||
use quinn::{AsyncUdpSocket, UdpPoller};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::io::IoSliceMut;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub struct ExtendedQuicSocket {
|
||||
inbound: QuicInnerInboundReceiver,
|
||||
outbound: QuicInnerOutbound,
|
||||
}
|
||||
impl Debug for ExtendedQuicSocket {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("QuicSocket").finish()
|
||||
}
|
||||
}
|
||||
impl ExtendedQuicSocket {
|
||||
pub fn new(inbound: QuicInnerInboundReceiver, outbound: QuicInnerOutbound) -> Self {
|
||||
Self { inbound, outbound }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncUdpSocket for ExtendedQuicSocket {
|
||||
fn create_io_poller(self: Arc<Self>) -> Pin<Box<dyn UdpPoller>> {
|
||||
self.outbound.create_io_poller()
|
||||
}
|
||||
|
||||
fn try_send(&self, transmit: &Transmit) -> std::io::Result<()> {
|
||||
let IpAddr::V4(dest) = transmit.destination.ip() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.outbound.try_outbound(transmit.contents, dest)
|
||||
}
|
||||
|
||||
fn poll_recv(
|
||||
&self,
|
||||
cx: &mut Context,
|
||||
bufs: &mut [IoSliceMut<'_>],
|
||||
meta: &mut [RecvMeta],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
self.inbound.poll_recv(cx, bufs, meta)
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||
Ok(SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::new(127, 0, 0, 1),
|
||||
10000,
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod enhanced_io;
|
||||
pub(crate) mod quic_client;
|
||||
pub(crate) mod quic_inbound;
|
||||
pub(crate) mod quic_outbound;
|
||||
mod quic_server;
|
||||
|
||||
pub(crate) mod boot;
|
||||
@@ -0,0 +1,309 @@
|
||||
use crate::context::AppState;
|
||||
|
||||
use crate::nat::SubnetExternalRoute;
|
||||
use crate::protocol::client_message::{
|
||||
IpProxyHandshake, QuicProxyHandshake, TcpProxyHandshake, quic_proxy_handshake,
|
||||
};
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use futures::SinkExt;
|
||||
use parking_lot::Mutex;
|
||||
use pnet_packet::ip::IpNextHeaderProtocol;
|
||||
use prost::Message;
|
||||
use quinn::{Connection, Endpoint, RecvStream, SendStream};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use tcp_ip::IpStack;
|
||||
use tcp_ip::ip::IpSocket;
|
||||
use tcp_ip::tcp::TcpStream;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::mpsc::error::TrySendError;
|
||||
use tokio_util::codec::{FramedWrite, LengthDelimitedCodec};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QuicTunnelClient {
|
||||
app_state: AppState,
|
||||
endpoint: Endpoint,
|
||||
connection_map: Arc<Mutex<HashMap<Ipv4Addr, Arc<OnceCell<Connection>>>>>,
|
||||
external_route: SubnetExternalRoute,
|
||||
}
|
||||
|
||||
impl QuicTunnelClient {
|
||||
pub fn new(
|
||||
app_state: AppState,
|
||||
endpoint: Endpoint,
|
||||
external_route: SubnetExternalRoute,
|
||||
) -> QuicTunnelClient {
|
||||
Self {
|
||||
app_state,
|
||||
endpoint,
|
||||
connection_map: Arc::new(Default::default()),
|
||||
external_route,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_bi(&self, mut dest: Ipv4Addr) -> anyhow::Result<(SendStream, RecvStream)> {
|
||||
let Some(net) = self.app_state.get_network() else {
|
||||
bail!("no network found");
|
||||
};
|
||||
if !net.network().contains(&dest) {
|
||||
if let Some(v) = self.external_route.route(&dest) {
|
||||
dest = v;
|
||||
} else {
|
||||
bail!("invalid route found:{dest}");
|
||||
}
|
||||
}
|
||||
let mut count = 0;
|
||||
loop {
|
||||
count += 1;
|
||||
let cell = self
|
||||
.connection_map
|
||||
.lock()
|
||||
.entry(dest)
|
||||
.or_insert_with(|| Arc::new(OnceCell::new()))
|
||||
.clone();
|
||||
let connection = cell
|
||||
.get_or_try_init(|| async {
|
||||
self.endpoint
|
||||
.connect(SocketAddr::new(dest.into(), 10000), "localhost")?
|
||||
.await
|
||||
.context("connect failed")
|
||||
})
|
||||
.await?;
|
||||
|
||||
return match connection.open_bi().await {
|
||||
Ok(rs) => Ok(rs),
|
||||
Err(e) => {
|
||||
self.connection_map.lock().remove(&dest);
|
||||
if count == 1 {
|
||||
continue;
|
||||
}
|
||||
Err(e.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
pub async fn open_uni(&self, mut dest: Ipv4Addr) -> anyhow::Result<SendStream> {
|
||||
let Some(net) = self.app_state.get_network() else {
|
||||
bail!("no network found");
|
||||
};
|
||||
if !net.network().contains(&dest) {
|
||||
if let Some(v) = self.external_route.route(&dest) {
|
||||
dest = v;
|
||||
} else {
|
||||
bail!("invalid route found:{dest}");
|
||||
}
|
||||
}
|
||||
let mut count = 0;
|
||||
loop {
|
||||
count += 1;
|
||||
let cell = self
|
||||
.connection_map
|
||||
.lock()
|
||||
.entry(dest)
|
||||
.or_insert_with(|| Arc::new(OnceCell::new()))
|
||||
.clone();
|
||||
let connection = cell
|
||||
.get_or_try_init(|| async {
|
||||
self.endpoint
|
||||
.connect(SocketAddr::new(dest.into(), 10000), "localhost")?
|
||||
.await
|
||||
.context("connect failed")
|
||||
})
|
||||
.await?;
|
||||
return match connection.open_uni().await {
|
||||
Ok(rs) => Ok(rs),
|
||||
Err(e) => {
|
||||
self.connection_map.lock().remove(&dest);
|
||||
if count == 1 {
|
||||
continue;
|
||||
}
|
||||
Err(e.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) async fn send_handshake(
|
||||
send_stream: &mut SendStream,
|
||||
handshake: QuicProxyHandshake,
|
||||
) -> anyhow::Result<()> {
|
||||
let handshake = handshake.encode_to_vec();
|
||||
send_stream.write_u16(handshake.len() as u16).await?;
|
||||
send_stream.write_all(&handshake).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_client(
|
||||
quic_client: QuicTunnelClient,
|
||||
task_group: TaskGroup,
|
||||
ip_stack: IpStack,
|
||||
ip_socket: Arc<IpSocket>,
|
||||
) {
|
||||
task_group.spawn(tcp_listen(
|
||||
task_group.clone(),
|
||||
ip_stack.clone(),
|
||||
quic_client.clone(),
|
||||
));
|
||||
task_group.spawn(ip_listen(task_group.clone(), ip_socket, quic_client));
|
||||
}
|
||||
|
||||
async fn tcp_listen(
|
||||
task_group: TaskGroup,
|
||||
ip_stack: IpStack,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) {
|
||||
if let Err(e) = tcp_listen_impl(task_group, ip_stack, quic_tunnel_client).await {
|
||||
log::error!("tcp_listen {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn tcp_listen_impl(
|
||||
task_group: TaskGroup,
|
||||
ip_stack: IpStack,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut listener = tcp_ip::tcp::TcpListener::bind_all(ip_stack).await?;
|
||||
loop {
|
||||
let (tcp_stream, addr) = listener.accept().await?;
|
||||
let quic_tunnel_client = quic_tunnel_client.clone();
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = tcp_stream_handle(tcp_stream, quic_tunnel_client).await {
|
||||
log::error!("TCP stream handle failed with error: {e:?},addr={addr}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn tcp_stream_handle(
|
||||
tcp_stream: TcpStream,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
// 连接方向是反过来的,因为自己充当目标做了tcp卸载
|
||||
let SocketAddr::V4(peer_addr) = tcp_stream.local_addr()? else {
|
||||
bail!("invalid IP address");
|
||||
};
|
||||
let SocketAddr::V4(local_addr) = tcp_stream.peer_addr()? else {
|
||||
bail!("invalid IP address");
|
||||
};
|
||||
log::debug!("connect TCP stream {}->{}", local_addr, peer_addr);
|
||||
|
||||
let (mut send_stream, mut recv_stream) = quic_tunnel_client.open_bi(*peer_addr.ip()).await?;
|
||||
let handshake = QuicProxyHandshake {
|
||||
handshake: Some(quic_proxy_handshake::Handshake::Tcp(TcpProxyHandshake {
|
||||
src_ip: (*local_addr.ip()).into(),
|
||||
src_port: local_addr.port().into(),
|
||||
dst_ip: (*peer_addr.ip()).into(),
|
||||
dst_port: peer_addr.port().into(),
|
||||
})),
|
||||
};
|
||||
send_handshake(&mut send_stream, handshake).await?;
|
||||
let (mut tcp_w, mut tcp_r) = tcp_stream.split()?;
|
||||
tokio::select! {
|
||||
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
|
||||
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
|
||||
}
|
||||
log::debug!("disconnect TCP stream {}->{}", local_addr, peer_addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ip_listen(
|
||||
task_group: TaskGroup,
|
||||
ip_socket: Arc<IpSocket>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) {
|
||||
if let Err(e) = ip_listen_impl(task_group, ip_socket, quic_tunnel_client).await {
|
||||
log::error!("ip_listen {e:?}");
|
||||
}
|
||||
}
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||
struct IpKey {
|
||||
protocol: IpNextHeaderProtocol,
|
||||
src: Ipv4Addr,
|
||||
dest: Ipv4Addr,
|
||||
}
|
||||
async fn ip_listen_impl(
|
||||
task_group: TaskGroup,
|
||||
ip_socket: Arc<IpSocket>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let dest_map = Arc::new(Mutex::new(HashMap::<IpKey, Sender<Bytes>>::new()));
|
||||
|
||||
loop {
|
||||
let (len, protocol, src, dest) = ip_socket.recv_protocol_from_to(&mut buf).await?;
|
||||
let (IpAddr::V4(src), IpAddr::V4(dest)) = (src, dest) else {
|
||||
continue;
|
||||
};
|
||||
let key = IpKey {
|
||||
protocol,
|
||||
src,
|
||||
dest,
|
||||
};
|
||||
let bytes = Bytes::copy_from_slice(&buf[..len]);
|
||||
|
||||
let tx = {
|
||||
let mut map = dest_map.lock();
|
||||
if let Some(tx) = map.get(&key) {
|
||||
tx.clone()
|
||||
} else {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(128);
|
||||
|
||||
spawn_dest_sender(task_group.clone(), key, rx, quic_tunnel_client.clone());
|
||||
|
||||
map.insert(key, tx.clone());
|
||||
tx
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = tx.try_send(bytes) {
|
||||
match err {
|
||||
TrySendError::Full(_) => {}
|
||||
TrySendError::Closed(_) => {
|
||||
let mut map = dest_map.lock();
|
||||
map.remove(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_dest_sender(
|
||||
task_group: TaskGroup,
|
||||
key: IpKey,
|
||||
mut rx: tokio::sync::mpsc::Receiver<Bytes>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) {
|
||||
log::info!("send ip({}) packet {}->{}", key.protocol, key.src, key.dest);
|
||||
task_group.spawn(async move {
|
||||
let result = async {
|
||||
let mut send_stream = quic_tunnel_client.open_uni(key.dest).await?;
|
||||
|
||||
let handshake = QuicProxyHandshake {
|
||||
handshake: Some(quic_proxy_handshake::Handshake::Ip(IpProxyHandshake {
|
||||
ip_next_header_protocol: key.protocol.0 as _,
|
||||
src_ip: key.src.into(),
|
||||
dst_ip: key.dest.into(),
|
||||
})),
|
||||
};
|
||||
send_handshake(&mut send_stream, handshake).await?;
|
||||
|
||||
let mut framed = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
|
||||
|
||||
while let Some(pkt) = rx.recv().await {
|
||||
framed.send(pkt).await?;
|
||||
}
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!("key {:?} sender task exit: {:?}", key, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use crate::enhanced_tunnel::quic_over::enhanced_io::enhanced_inbound::QuicDataInbound;
|
||||
use bytes::Bytes;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EnhancedQuicInbound {
|
||||
quic_data_inbound: QuicDataInbound,
|
||||
}
|
||||
|
||||
impl EnhancedQuicInbound {
|
||||
pub fn new(quic_data_inbound: QuicDataInbound) -> Self {
|
||||
Self { quic_data_inbound }
|
||||
}
|
||||
pub async fn inbound(&self, data: Bytes, src: Ipv4Addr) -> anyhow::Result<()> {
|
||||
self.quic_data_inbound.send(data, src).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::context::NetworkAddr;
|
||||
use pnet_packet::Packet;
|
||||
use pnet_packet::ip::IpNextHeaderProtocols;
|
||||
use pnet_packet::ipv4::{Ipv4Flags, Ipv4Packet};
|
||||
use pnet_packet::tcp::TcpFlags::{ACK, SYN};
|
||||
use pnet_packet::tcp::TcpPacket;
|
||||
use std::net::SocketAddr;
|
||||
use tcp_ip::{IpStack, IpStackSend};
|
||||
|
||||
pub struct EnhancedQuicOutbound {
|
||||
open_quic_client: bool,
|
||||
ip_stack_send: IpStackSend,
|
||||
ip_stack: IpStack,
|
||||
}
|
||||
|
||||
impl EnhancedQuicOutbound {
|
||||
pub fn new(open_quic_client: bool, ip_stack_send: IpStackSend, ip_stack: IpStack) -> Self {
|
||||
Self {
|
||||
open_quic_client,
|
||||
ip_stack_send,
|
||||
ip_stack,
|
||||
}
|
||||
}
|
||||
pub async fn outbound(&self, _net: &NetworkAddr, data: &[u8]) -> bool {
|
||||
let Some(ipv4) = Ipv4Packet::new(data) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
if self.open_quic_client {
|
||||
// 针对tcp 如果不是从IpStack建立的连接,则不使用IpStack解析
|
||||
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Tcp {
|
||||
let more_fragments =
|
||||
ipv4.get_flags() & Ipv4Flags::MoreFragments == Ipv4Flags::MoreFragments;
|
||||
let offset = ipv4.get_fragment_offset();
|
||||
let segmented = more_fragments || offset > 0;
|
||||
if !segmented {
|
||||
let Some(tcp) = TcpPacket::new(ipv4.payload()) else {
|
||||
return true;
|
||||
};
|
||||
// 不是第一个包
|
||||
if !(tcp.get_flags() & SYN == SYN && tcp.get_flags() & ACK != ACK) {
|
||||
let local_addr =
|
||||
SocketAddr::new(ipv4.get_source().into(), tcp.get_source());
|
||||
let peer_addr =
|
||||
SocketAddr::new(ipv4.get_destination().into(), tcp.get_destination());
|
||||
// 在IpStack中找不到连接
|
||||
if !self
|
||||
.ip_stack
|
||||
.has_tcp_connection(local_addr, peer_addr)
|
||||
.unwrap_or(false)
|
||||
&& !self
|
||||
.ip_stack
|
||||
.has_tcp_connection(peer_addr, local_addr)
|
||||
.unwrap_or(false)
|
||||
&& !self
|
||||
.ip_stack
|
||||
.has_tcp_half_open(peer_addr, local_addr)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = self.ip_stack_send.send_ip_packet(data).await;
|
||||
return true;
|
||||
}
|
||||
// 判断tcp流
|
||||
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Tcp {
|
||||
let more_fragments =
|
||||
ipv4.get_flags() & Ipv4Flags::MoreFragments == Ipv4Flags::MoreFragments;
|
||||
let offset = ipv4.get_fragment_offset();
|
||||
let segmented = more_fragments || offset > 0;
|
||||
if !segmented && let Some(tcp) = TcpPacket::new(ipv4.payload()) {
|
||||
// 如果对端使用IpStack连接了自己,则也需要原路回复
|
||||
// 这是连接回复,所以方向是和流方向相反的
|
||||
let peer_addr = SocketAddr::new(ipv4.get_source().into(), tcp.get_source());
|
||||
let local_addr =
|
||||
SocketAddr::new(ipv4.get_destination().into(), tcp.get_destination());
|
||||
|
||||
if self
|
||||
.ip_stack
|
||||
.has_tcp_connection(local_addr, peer_addr)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
_ = self.ip_stack_send.send_ip_packet(data).await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
use crate::nat::internal_nat::{InternalNatInbound, PortMappingManager};
|
||||
use crate::protocol::client_message::QuicProxyHandshake;
|
||||
use crate::protocol::client_message::quic_proxy_handshake::Handshake;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::{Context, bail};
|
||||
use futures::StreamExt;
|
||||
use pnet_packet::ip::IpNextHeaderProtocol;
|
||||
use prost::Message;
|
||||
use quinn::{Connection, Endpoint, RecvStream, SendStream};
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use tcp_ip::IpStack;
|
||||
use tcp_ip::ip::IpSocket;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::codec::{FramedRead, LengthDelimitedCodec};
|
||||
|
||||
pub async fn server_listen(
|
||||
task_group: &TaskGroup,
|
||||
endpoint: Endpoint,
|
||||
ip_socket: Option<Arc<IpSocket>>,
|
||||
ip_stack: Option<IpStack>,
|
||||
internal_nat_manager: Option<InternalNatInbound>,
|
||||
port_mapping_manager: PortMappingManager,
|
||||
) {
|
||||
task_group.spawn(quic_endpoint_accept(
|
||||
ip_stack,
|
||||
task_group.clone(),
|
||||
endpoint,
|
||||
ip_socket,
|
||||
internal_nat_manager,
|
||||
port_mapping_manager,
|
||||
));
|
||||
}
|
||||
|
||||
async fn quic_endpoint_accept(
|
||||
ip_stack: Option<IpStack>,
|
||||
task_group: TaskGroup,
|
||||
endpoint: Endpoint,
|
||||
ip_socket: Option<Arc<IpSocket>>,
|
||||
internal_nat_manager: Option<InternalNatInbound>,
|
||||
port_mapping_manager: PortMappingManager,
|
||||
) {
|
||||
while let Some(connecting) = endpoint.accept().await {
|
||||
let remote_addr = connecting.remote_address();
|
||||
let task_group_clone = task_group.clone();
|
||||
let ip_socket = ip_socket.clone();
|
||||
let ip_stack = ip_stack.clone();
|
||||
let internal_nat_manager = internal_nat_manager.clone();
|
||||
let port_mapping_manager = port_mapping_manager.clone();
|
||||
task_group.spawn(async move {
|
||||
match connecting.await {
|
||||
Ok(connection) => {
|
||||
log::info!("QUIC connection: {}", remote_addr);
|
||||
if let Err(e) = quic_accept(
|
||||
ip_stack,
|
||||
task_group_clone,
|
||||
connection,
|
||||
ip_socket,
|
||||
internal_nat_manager,
|
||||
port_mapping_manager,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::info!("quic close: {remote_addr},{e:?}",);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("connect: {:?},remote_addr={remote_addr}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
log::warn!("quic server closed");
|
||||
}
|
||||
|
||||
async fn quic_accept(
|
||||
ip_stack: Option<IpStack>,
|
||||
task_group_clone: TaskGroup,
|
||||
connection: Connection,
|
||||
ip_socket: Option<Arc<IpSocket>>,
|
||||
internal_nat_manager: Option<InternalNatInbound>,
|
||||
port_mapping_manager: PortMappingManager,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
tokio::select! {
|
||||
rs = connection.accept_bi()=>{
|
||||
let (send_stream, recv_stream) = rs?;
|
||||
let ip_stack = ip_stack.clone();
|
||||
let internal_nat_manager = internal_nat_manager.clone();
|
||||
let port_mapping_manager = port_mapping_manager.clone();
|
||||
task_group_clone.spawn(async move {
|
||||
if let Err(e) = quic_stream_bi_handle(ip_stack,send_stream, recv_stream,&internal_nat_manager,port_mapping_manager).await{
|
||||
log::error!("quic_stream_bi_handle: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
rs = connection.accept_uni()=>{
|
||||
let recv_stream = rs?;
|
||||
let ip_socket = ip_socket.clone();
|
||||
let internal_nat_manager = internal_nat_manager.clone();
|
||||
task_group_clone.spawn(async move {
|
||||
if let Err(e) = quic_stream_uni_handle(recv_stream, ip_socket,&internal_nat_manager).await{
|
||||
log::error!("quic_stream_uni_handle: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn quic_stream_bi_handle(
|
||||
ip_stack: Option<IpStack>,
|
||||
mut send_stream: SendStream,
|
||||
mut recv_stream: RecvStream,
|
||||
internal_nat_manager: &Option<InternalNatInbound>,
|
||||
port_mapping_manager: PortMappingManager,
|
||||
) -> anyhow::Result<()> {
|
||||
let handshake = recv_handshake(&mut recv_stream).await?;
|
||||
let Some(handshake) = handshake.handshake else {
|
||||
return Ok(());
|
||||
};
|
||||
match handshake {
|
||||
Handshake::Tcp(handshake) => {
|
||||
let src = SocketAddr::new(
|
||||
Ipv4Addr::from(handshake.src_ip).into(),
|
||||
handshake.src_port as _,
|
||||
);
|
||||
let dst_ip = Ipv4Addr::from(handshake.dst_ip);
|
||||
let dst = SocketAddr::new(dst_ip.into(), handshake.dst_port as _);
|
||||
if src == dst {
|
||||
bail!("tcp handshake failed, ip: {}", src);
|
||||
}
|
||||
log::debug!("accept TCP stream {src}->{dst}");
|
||||
// 如果不是网段内的,并且启用了内置nat,则直接转发
|
||||
if let Some(internal_nat_manager) = internal_nat_manager {
|
||||
if internal_nat_manager.use_nat(&dst_ip) {
|
||||
internal_nat_manager
|
||||
.tcp_nat(recv_stream, send_stream, dst_ip, dst.port())
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
if internal_nat_manager.no_tun() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(ip_stack) = ip_stack {
|
||||
let stream = tcp_ip::tcp::TcpStream::bind(ip_stack, src)?
|
||||
.connect_to(dst)
|
||||
.await?;
|
||||
let (mut tcp_w, mut tcp_r) = stream.split()?;
|
||||
tokio::select! {
|
||||
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
|
||||
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
|
||||
}
|
||||
log::debug!("accept close TCP stream {src}->{dst}");
|
||||
}
|
||||
}
|
||||
Handshake::Ip(_) => {}
|
||||
Handshake::TcpPortMapping(handshake) => {
|
||||
port_mapping_manager
|
||||
.tcp_mapping(
|
||||
recv_stream,
|
||||
send_stream,
|
||||
handshake.dst_host,
|
||||
handshake.dst_port as _,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Handshake::UdpPortMapping(handshake) => {
|
||||
port_mapping_manager
|
||||
.udp_mapping(
|
||||
recv_stream,
|
||||
send_stream,
|
||||
handshake.dst_host,
|
||||
handshake.dst_port as _,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv_handshake(recv_stream: &mut RecvStream) -> anyhow::Result<QuicProxyHandshake> {
|
||||
let len = recv_stream.read_u16().await?;
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
recv_stream.read_exact(&mut buf).await?;
|
||||
let handshake = QuicProxyHandshake::decode(&buf[..])?;
|
||||
Ok(handshake)
|
||||
}
|
||||
async fn quic_stream_uni_handle(
|
||||
mut recv_stream: RecvStream,
|
||||
ip_socket: Option<Arc<IpSocket>>,
|
||||
internal_nat_manager: &Option<InternalNatInbound>,
|
||||
) -> anyhow::Result<()> {
|
||||
let handshake = recv_handshake(&mut recv_stream).await?;
|
||||
let Some(handshake) = handshake.handshake else {
|
||||
return Ok(());
|
||||
};
|
||||
match handshake {
|
||||
Handshake::Tcp(_) => {}
|
||||
Handshake::Ip(handshake) => {
|
||||
let ip_next_header_protocol =
|
||||
IpNextHeaderProtocol::new(handshake.ip_next_header_protocol as _);
|
||||
let src_ip = Ipv4Addr::from(handshake.src_ip);
|
||||
let dest_ip = Ipv4Addr::from(handshake.dst_ip);
|
||||
log::debug!("recv IP({ip_next_header_protocol}) packet {src_ip}->{dest_ip}");
|
||||
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
|
||||
// 如果不是网段内的,并且启用了内置nat,则直接转发
|
||||
if let Some(internal_nat_manager) = internal_nat_manager {
|
||||
if internal_nat_manager.use_nat(&dest_ip) {
|
||||
loop {
|
||||
let buf = framed_read
|
||||
.next()
|
||||
.await
|
||||
.context("receive quic stream failed")??;
|
||||
internal_nat_manager
|
||||
.send_ipv4_payload(ip_next_header_protocol, src_ip, dest_ip, buf)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
if internal_nat_manager.no_tun() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let Some(ip_socket) = ip_socket else {
|
||||
return Ok(());
|
||||
};
|
||||
let src_ip = src_ip.into();
|
||||
let dest_ip = dest_ip.into();
|
||||
loop {
|
||||
let buf = framed_read
|
||||
.next()
|
||||
.await
|
||||
.context("receive quic stream failed")??;
|
||||
|
||||
ip_socket
|
||||
.send_protocol_from_to(&buf, ip_next_header_protocol, src_ip, dest_ip)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Handshake::TcpPortMapping(_) => {}
|
||||
Handshake::UdpPortMapping(_) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
use crate::fec::encoder::FecPacket;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use anyhow::{Result, bail};
|
||||
use prost::Message;
|
||||
use reed_solomon_erasure::galois_8::ReedSolomon;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const GROUP_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
const MAX_GROUPS: usize = 1000;
|
||||
const MAX_NUM: usize = 50;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FecDecoder {
|
||||
inner: Arc<parking_lot::Mutex<FecDecoderInner>>,
|
||||
}
|
||||
|
||||
struct FecDecoderInner {
|
||||
groups: HashMap<(Ipv4Addr, u64), FecGroup>,
|
||||
last_cleanup: Instant,
|
||||
}
|
||||
|
||||
struct FecGroup {
|
||||
data_shards: usize,
|
||||
parity_shards: usize,
|
||||
received_original_count: usize,
|
||||
received_shards: Vec<Option<Vec<u8>>>,
|
||||
last_update: Instant,
|
||||
}
|
||||
impl FecGroup {
|
||||
fn is_done(&self) -> bool {
|
||||
self.data_shards != 0 && self.received_original_count == self.data_shards
|
||||
}
|
||||
fn done(&mut self) {
|
||||
self.received_original_count = self.data_shards;
|
||||
self.received_shards = vec![];
|
||||
}
|
||||
}
|
||||
impl Default for FecGroup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data_shards: 0,
|
||||
parity_shards: 0,
|
||||
received_original_count: 0,
|
||||
received_shards: Vec::with_capacity(16),
|
||||
last_update: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FecDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(parking_lot::Mutex::new(FecDecoderInner {
|
||||
groups: HashMap::new(),
|
||||
last_cleanup: Instant::now(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// 接收FEC包并尝试恢复丢失的包
|
||||
pub fn receive(
|
||||
&self,
|
||||
net_packet: NetPacket<TransmissionBytes>,
|
||||
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
|
||||
let mut inner = self.inner.lock();
|
||||
let src_ip = Ipv4Addr::from(net_packet.src_id());
|
||||
let fec_packet = FecPacket::decode(net_packet.payload())?;
|
||||
|
||||
let group_id = fec_packet.group_id;
|
||||
let packet_index = fec_packet.packet_index as usize;
|
||||
let payload = fec_packet.payload;
|
||||
|
||||
if packet_index > MAX_NUM {
|
||||
log::warn!(
|
||||
"packet_index overflow, src={src_ip},group_id={group_id}, packet_index={packet_index}",
|
||||
);
|
||||
bail!("packet_index overflow {src_ip}");
|
||||
}
|
||||
let mut packet = None;
|
||||
let group = inner.groups.entry((src_ip, group_id)).or_default();
|
||||
if group.is_done() {
|
||||
return Ok(None);
|
||||
}
|
||||
if group
|
||||
.received_shards
|
||||
.get(packet_index)
|
||||
.is_some_and(|v| v.is_some())
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(parity_data) = fec_packet.parity_data {
|
||||
let data_shards = parity_data.data_shards as usize;
|
||||
let parity_shards = parity_data.parity_shards as usize;
|
||||
if data_shards > MAX_NUM {
|
||||
bail!("data_shards overflow {src_ip}");
|
||||
}
|
||||
if parity_shards > MAX_NUM {
|
||||
bail!("parity_shards overflow {src_ip}");
|
||||
}
|
||||
|
||||
if data_shards + parity_shards <= packet_index {
|
||||
log::warn!(
|
||||
"packet_index overflow in parity, src={},group_id={}, packet_index={}, total_shards={}",
|
||||
src_ip,
|
||||
group_id,
|
||||
packet_index,
|
||||
data_shards + parity_shards
|
||||
);
|
||||
bail!("packet_index overflow {src_ip}");
|
||||
}
|
||||
if group.data_shards != 0 && group.data_shards != data_shards {
|
||||
bail!("group data_shards!=data_shards {src_ip}");
|
||||
}
|
||||
if group.parity_shards != 0 && group.parity_shards != parity_shards {
|
||||
bail!("group parity_shards!=parity_shards {src_ip}");
|
||||
}
|
||||
group.data_shards = data_shards;
|
||||
group.parity_shards = parity_shards;
|
||||
if group.received_shards.len() < data_shards + parity_shards {
|
||||
group
|
||||
.received_shards
|
||||
.resize(data_shards + parity_shards, None);
|
||||
}
|
||||
|
||||
group.received_shards[packet_index] = Some(payload);
|
||||
} else {
|
||||
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + payload.len());
|
||||
let mut result_packet = NetPacket::new(buffer)?;
|
||||
result_packet.head_mut().copy_from_slice(net_packet.head());
|
||||
result_packet.set_fec_flag(false);
|
||||
result_packet.set_payload(&payload)?;
|
||||
packet = Some(result_packet);
|
||||
|
||||
if group.received_shards.len() <= packet_index {
|
||||
group.received_shards.resize(packet_index + 1, None);
|
||||
}
|
||||
|
||||
// 保存FEC数据: [type_byte, flags_byte, payload_len(u16), payload...]
|
||||
let type_byte = net_packet.head()[0];
|
||||
let flags_byte = net_packet.head()[2];
|
||||
let mut batch_data = vec![0u8; 4 + payload.len()];
|
||||
batch_data[0] = type_byte;
|
||||
batch_data[1] = flags_byte;
|
||||
batch_data[2..4].copy_from_slice(&(payload.len() as u16).to_be_bytes());
|
||||
batch_data[4..].copy_from_slice(&payload);
|
||||
group.received_shards[packet_index] = Some(batch_data);
|
||||
group.received_original_count += 1;
|
||||
}
|
||||
group.last_update = Instant::now();
|
||||
|
||||
if group.is_done() {
|
||||
group.done();
|
||||
if inner.last_cleanup.elapsed() > Duration::from_secs(1) {
|
||||
Self::cleanup_old_groups(&mut inner.groups);
|
||||
inner.last_cleanup = Instant::now();
|
||||
}
|
||||
return Ok(packet.map(|v| vec![v]));
|
||||
}
|
||||
|
||||
let result = Self::try_decode(group, (src_ip, group_id), &net_packet)?;
|
||||
|
||||
if inner.last_cleanup.elapsed() > Duration::from_secs(1) {
|
||||
Self::cleanup_old_groups(&mut inner.groups);
|
||||
inner.last_cleanup = Instant::now();
|
||||
}
|
||||
match (packet, result) {
|
||||
(Some(packet), Some(mut result)) => {
|
||||
result.push(packet);
|
||||
Ok(Some(result))
|
||||
}
|
||||
(Some(packet), None) => Ok(Some(vec![packet])),
|
||||
(None, Some(result)) => Ok(Some(result)),
|
||||
(None, None) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否可以恢复丢失的包
|
||||
fn try_decode(
|
||||
group: &mut FecGroup,
|
||||
key: (Ipv4Addr, u64),
|
||||
net_packet: &NetPacket<TransmissionBytes>,
|
||||
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
|
||||
if group.data_shards == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if group.received_original_count == group.data_shards {
|
||||
return Ok(None);
|
||||
}
|
||||
let received_count = group.received_shards.iter().filter(|s| s.is_some()).count();
|
||||
|
||||
if received_count < group.data_shards {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Self::decode_with_rs(group, key, net_packet)
|
||||
}
|
||||
|
||||
/// Reed-Solomon解码恢复丢失的包
|
||||
fn decode_with_rs(
|
||||
group: &mut FecGroup,
|
||||
key: (Ipv4Addr, u64),
|
||||
net_packet: &NetPacket<TransmissionBytes>,
|
||||
) -> Result<Option<Vec<NetPacket<TransmissionBytes>>>> {
|
||||
let (src_ip, group_id) = key;
|
||||
|
||||
if group.received_shards.len() != group.data_shards + group.parity_shards {
|
||||
bail!(
|
||||
"received_shards.len()({}) != data_shards({})+parity_shards({}) src_ip={src_ip},group_id={group_id}",
|
||||
group.received_shards.len(),
|
||||
group.data_shards,
|
||||
group.parity_shards,
|
||||
)
|
||||
}
|
||||
|
||||
let mut delivered_packets = vec![false; group.data_shards];
|
||||
for (index, x) in group.received_shards[..group.data_shards]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if x.is_some() {
|
||||
delivered_packets[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let rs = ReedSolomon::new(group.data_shards, group.parity_shards)?;
|
||||
rs.reconstruct(&mut group.received_shards)?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for (i, shard) in group
|
||||
.received_shards
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(group.data_shards)
|
||||
{
|
||||
if let Some(shard) = shard {
|
||||
if delivered_packets[i] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let net_packet = Self::rebuild_net_packet(net_packet, shard)?;
|
||||
result.push(net_packet);
|
||||
}
|
||||
}
|
||||
|
||||
group.done();
|
||||
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
/// 从恢复的数据重建NetPacket
|
||||
fn rebuild_net_packet(
|
||||
current_packet: &NetPacket<TransmissionBytes>,
|
||||
recovered_data: &[u8],
|
||||
) -> Result<NetPacket<TransmissionBytes>> {
|
||||
if recovered_data.len() < 4 {
|
||||
bail!("recovered_data too short");
|
||||
}
|
||||
|
||||
let type_byte = recovered_data[0];
|
||||
let flags_byte = recovered_data[1];
|
||||
let payload_len = u16::from_be_bytes([recovered_data[2], recovered_data[3]]) as usize;
|
||||
|
||||
if payload_len + 4 > recovered_data.len() {
|
||||
bail!("invalid payload_len in recovered_data");
|
||||
}
|
||||
|
||||
let payload = &recovered_data[4..4 + payload_len];
|
||||
|
||||
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + payload.len());
|
||||
let mut net_packet = NetPacket::new(buffer)?;
|
||||
|
||||
net_packet.head_mut()[0] = type_byte;
|
||||
net_packet.head_mut()[2] = flags_byte;
|
||||
net_packet.set_src_id(current_packet.src_id());
|
||||
net_packet.set_dest_id(current_packet.dest_id());
|
||||
net_packet.set_ttl(current_packet.ttl());
|
||||
net_packet.set_fec_flag(false);
|
||||
net_packet.set_payload(payload)?;
|
||||
|
||||
Ok(net_packet)
|
||||
}
|
||||
|
||||
fn cleanup_old_groups(groups: &mut HashMap<(Ipv4Addr, u64), FecGroup>) {
|
||||
let now = Instant::now();
|
||||
|
||||
groups.retain(|_, group| now.duration_since(group.last_update) < GROUP_TIMEOUT);
|
||||
|
||||
if groups.len() > MAX_GROUPS {
|
||||
let mut group_ids: Vec<_> = groups.iter().map(|(id, g)| (*id, g.last_update)).collect();
|
||||
group_ids.sort_by_key(|(_, last_update)| *last_update);
|
||||
|
||||
let to_remove = group_ids.len() - MAX_GROUPS;
|
||||
for (group_id, _) in group_ids.iter().take(to_remove) {
|
||||
groups.remove(group_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::outbound::BasicOutbound;
|
||||
use anyhow::{Result, bail};
|
||||
use parking_lot::Mutex;
|
||||
use prost::Message;
|
||||
use reed_solomon_erasure::galois_8::ReedSolomon;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod fec_proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.fec.rs"));
|
||||
}
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
pub use fec_proto::FecPacket;
|
||||
|
||||
const BATCH_SIZE: usize = 10;
|
||||
const REDUNDANCY_RATE: f32 = 0.2;
|
||||
const BATCH_TIMEOUT_MS: u64 = 20;
|
||||
const MIN_PARITY: usize = 1;
|
||||
const BATCH_CHANNEL_SIZE: usize = 1024;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FecEncoder {
|
||||
batch_states: Arc<Mutex<HashMap<Ipv4Addr, DestBatchState>>>,
|
||||
batch_tx: mpsc::Sender<(Ipv4Addr, Ipv4Addr, u64, Vec<TransmissionBytes>)>,
|
||||
}
|
||||
|
||||
struct DestBatchState {
|
||||
group_id: u64,
|
||||
current_batch: Vec<TransmissionBytes>,
|
||||
deadline: Instant,
|
||||
src_ip: Ipv4Addr,
|
||||
}
|
||||
|
||||
impl FecEncoder {
|
||||
pub fn new(task_group: &TaskGroup, basic_outbound: BasicOutbound) -> Self {
|
||||
let (batch_tx, batch_rx) = mpsc::channel(BATCH_CHANNEL_SIZE);
|
||||
let batch_states = Arc::new(Mutex::new(HashMap::new()));
|
||||
let encoder = Self {
|
||||
batch_states: batch_states.clone(),
|
||||
batch_tx,
|
||||
};
|
||||
|
||||
task_group.spawn(fec_encoder_worker(batch_rx, basic_outbound, batch_states));
|
||||
|
||||
encoder
|
||||
}
|
||||
|
||||
/// 将数据包加入FEC批次并返回包装后的包
|
||||
pub fn encode(
|
||||
&self,
|
||||
mut packet: NetPacket<TransmissionBytes>,
|
||||
) -> Result<NetPacket<TransmissionBytes>> {
|
||||
let src_ip = Ipv4Addr::from(packet.src_id());
|
||||
let dest = Ipv4Addr::from(packet.dest_id());
|
||||
if packet.payload().len() > u16::MAX as usize {
|
||||
bail!("Payload too big");
|
||||
}
|
||||
let original_payload = packet.payload().to_vec();
|
||||
let original_payload_len = original_payload.len();
|
||||
|
||||
let type_byte = packet.head()[0];
|
||||
let flags_byte = packet.head()[2];
|
||||
|
||||
// 组装FEC数据: [type_byte, flags_byte, payload_len(u16), payload...]
|
||||
let batch_len = 4 + original_payload_len;
|
||||
let mut batch_buffer = TransmissionBytes::zeroed(batch_len);
|
||||
batch_buffer[0] = type_byte;
|
||||
batch_buffer[1] = flags_byte;
|
||||
batch_buffer[2..4].copy_from_slice(&(original_payload_len as u16).to_be_bytes());
|
||||
batch_buffer[4..batch_len].copy_from_slice(&original_payload);
|
||||
|
||||
let (group_id, packet_index) = {
|
||||
let mut states = self.batch_states.lock();
|
||||
let state = states.entry(dest).or_insert_with(|| DestBatchState {
|
||||
group_id: 0,
|
||||
current_batch: Vec::with_capacity(BATCH_SIZE),
|
||||
deadline: Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS),
|
||||
src_ip,
|
||||
});
|
||||
|
||||
let group_id = state.group_id;
|
||||
let packet_index = state.current_batch.len();
|
||||
|
||||
state.current_batch.push(batch_buffer);
|
||||
|
||||
if state.current_batch.len() >= BATCH_SIZE {
|
||||
let batch = std::mem::take(&mut state.current_batch);
|
||||
state.group_id += 1;
|
||||
state.deadline = Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS);
|
||||
|
||||
if self
|
||||
.batch_tx
|
||||
.try_send((src_ip, dest, group_id, batch))
|
||||
.is_err()
|
||||
{
|
||||
log::warn!(
|
||||
"failed to send batch to worker (channel full), dest={}, group_id={}",
|
||||
dest,
|
||||
group_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(group_id, packet_index)
|
||||
};
|
||||
|
||||
let fec_packet = FecPacket {
|
||||
group_id,
|
||||
packet_index: packet_index as u32,
|
||||
payload: original_payload,
|
||||
parity_data: None,
|
||||
};
|
||||
|
||||
let fec_payload = fec_packet.encode_to_vec();
|
||||
packet
|
||||
.source_buf_mut()
|
||||
.resize(HEAD_LENGTH + fec_payload.len(), 0);
|
||||
packet.set_payload(&fec_payload)?;
|
||||
packet.set_fec_flag(true);
|
||||
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
/// 后台worker,处理满批次和超时批次
|
||||
async fn fec_encoder_worker(
|
||||
mut batch_rx: mpsc::Receiver<(Ipv4Addr, Ipv4Addr, u64, Vec<TransmissionBytes>)>,
|
||||
basic_outbound: BasicOutbound,
|
||||
batch_states: Arc<Mutex<HashMap<Ipv4Addr, DestBatchState>>>,
|
||||
) {
|
||||
let mut timer = tokio::time::interval(Duration::from_millis(5));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some((src,dest, group_id, mut items)) = batch_rx.recv() => {
|
||||
if let Err(e) = encode_and_send_parity(src,dest, group_id, &mut items, &basic_outbound).await {
|
||||
log::warn!("encode_and_send_parity error for {} group {}: {:?}", dest, group_id, e);
|
||||
}
|
||||
}
|
||||
|
||||
_ = timer.tick() => {
|
||||
let now = Instant::now();
|
||||
|
||||
let timeout_batches = {
|
||||
let mut states = batch_states.lock();
|
||||
let mut batches = Vec::new();
|
||||
for (dest, state) in states.iter_mut() {
|
||||
if !state.current_batch.is_empty() && now >= state.deadline {
|
||||
let items = std::mem::take(&mut state.current_batch);
|
||||
let group_id = state.group_id;
|
||||
state.group_id += 1;
|
||||
state.deadline = Instant::now() + Duration::from_millis(BATCH_TIMEOUT_MS);
|
||||
batches.push((state.src_ip,*dest, group_id, items));
|
||||
}
|
||||
}
|
||||
batches
|
||||
};
|
||||
|
||||
for (src,dest, group_id, mut items) in timeout_batches {
|
||||
if let Err(e) = encode_and_send_parity(src, dest, group_id, &mut items, &basic_outbound).await {
|
||||
log::warn!("encode_and_send_parity timeout error for {} group {}: {:?}", dest, group_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reed-Solomon编码并发送冗余包
|
||||
async fn encode_and_send_parity(
|
||||
src: Ipv4Addr,
|
||||
dest: Ipv4Addr,
|
||||
group_id: u64,
|
||||
items: &mut Vec<TransmissionBytes>,
|
||||
basic_outbound: &BasicOutbound,
|
||||
) -> Result<()> {
|
||||
if items.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data_shards = items.len();
|
||||
let parity_shards = (data_shards as f32 * REDUNDANCY_RATE).ceil() as usize;
|
||||
let parity_shards = parity_shards.max(MIN_PARITY);
|
||||
|
||||
let max_len = items.iter().map(|buf| buf.len()).max().unwrap_or(0);
|
||||
|
||||
if max_len == 0 {
|
||||
log::warn!("max_len is 0, dest={}, group_id={}", dest, group_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for buf in items.iter_mut() {
|
||||
if buf.len() < max_len {
|
||||
let padding = max_len - buf.len();
|
||||
buf.extend_end(padding);
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..parity_shards {
|
||||
items.push(TransmissionBytes::zeroed(max_len));
|
||||
}
|
||||
|
||||
let rs = ReedSolomon::new(data_shards, parity_shards)?;
|
||||
let mut shard_refs: Vec<&mut [u8]> = items.iter_mut().map(|buf| buf.as_mut()).collect();
|
||||
rs.encode(&mut shard_refs)?;
|
||||
|
||||
for (i, parity_buf) in items[data_shards..].iter().enumerate() {
|
||||
let packet_index = (data_shards + i) as u32;
|
||||
let fec_packet = FecPacket {
|
||||
group_id,
|
||||
packet_index,
|
||||
payload: parity_buf.as_ref().to_vec(),
|
||||
parity_data: Some(fec_proto::ParityData {
|
||||
data_shards: data_shards as u32,
|
||||
parity_shards: parity_shards as u32,
|
||||
}),
|
||||
};
|
||||
|
||||
let fec_payload = fec_packet.encode_to_vec();
|
||||
|
||||
let buffer = TransmissionBytes::zeroed(HEAD_LENGTH + fec_payload.len());
|
||||
let mut net_packet = NetPacket::new(buffer)?;
|
||||
net_packet.set_msg_type(MsgType::Turn);
|
||||
net_packet.set_src_id(src.into());
|
||||
net_packet.set_dest_id(dest.into());
|
||||
net_packet.set_ttl(5);
|
||||
net_packet.set_payload(&fec_payload)?;
|
||||
net_packet.set_fec_flag(true);
|
||||
|
||||
if let Err(e) = basic_outbound.send_encrypted_packet(dest, net_packet).await {
|
||||
log::warn!(
|
||||
"failed to send parity packet {}: {:?}, dest={}, group_id={}",
|
||||
packet_index,
|
||||
e,
|
||||
dest,
|
||||
group_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod decoder;
|
||||
mod encoder;
|
||||
|
||||
pub(crate) use decoder::FecDecoder;
|
||||
pub(crate) use encoder::FecEncoder;
|
||||
@@ -0,0 +1,15 @@
|
||||
pub(crate) mod compression;
|
||||
pub mod context;
|
||||
pub mod core;
|
||||
pub mod crypto;
|
||||
pub(crate) mod fec;
|
||||
pub mod nat;
|
||||
pub mod protocol;
|
||||
pub mod tls;
|
||||
pub(crate) mod tun;
|
||||
pub mod tunnel_core;
|
||||
pub mod utils;
|
||||
|
||||
pub mod api;
|
||||
pub(crate) mod enhanced_tunnel;
|
||||
pub mod port_mapping;
|
||||
@@ -0,0 +1,139 @@
|
||||
use crate::context::SharedNetworkAddr;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use pnet_packet::Packet;
|
||||
use pnet_packet::icmp::echo_reply::{Identifier, SequenceNumber};
|
||||
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use tcp_ip::IpStack;
|
||||
use tcp_ip::icmp::IcmpSocket;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn start_icmp_nat(
|
||||
task_group: &TaskGroup,
|
||||
ip_stack: &IpStack,
|
||||
no_tun: bool,
|
||||
network: SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let net_icmp_socket = socket2::Socket::new(
|
||||
socket2::Domain::IPV4,
|
||||
socket2::Type::RAW,
|
||||
Some(socket2::Protocol::ICMPV4),
|
||||
)
|
||||
.context("new Socket RAW ICMPV4 failed")?;
|
||||
let addr: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
|
||||
net_icmp_socket
|
||||
.bind(&socket2::SockAddr::from(addr))
|
||||
.context("bind Socket ICMPV4 failed")?;
|
||||
net_icmp_socket.set_nonblocking(true)?;
|
||||
|
||||
let std_socket: std::net::UdpSocket = net_icmp_socket.into();
|
||||
|
||||
let tokio_icmp_socket = UdpSocket::from_std(std_socket)?;
|
||||
|
||||
let inner_icmp_socket = IcmpSocket::bind_all(ip_stack.clone()).await?;
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = task(tokio_icmp_socket, inner_icmp_socket, no_tun, network).await {
|
||||
log::error!("icmp task failed: {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
async fn task(
|
||||
tokio_icmp_socket: UdpSocket,
|
||||
inner_icmp_socket: IcmpSocket,
|
||||
no_tun: bool,
|
||||
network: SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut buf1 = vec![0u8; 65536];
|
||||
let mut buf2 = vec![0u8; 65536];
|
||||
let mut map = HashMap::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
rs = tokio_icmp_socket.recv(&mut buf1) => {
|
||||
let len = rs?;
|
||||
tokio_icmp_socket_recv(&buf1[..len],&inner_icmp_socket,&map,no_tun,&network).await?;
|
||||
}
|
||||
rs = inner_icmp_socket.recv_from_to(&mut buf2) => {
|
||||
let (len,src,dst) = rs?;
|
||||
inner_icmp_socket_recv(&buf2[..len],src,dst,&tokio_icmp_socket,&mut map,no_tun,&network).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn tokio_icmp_socket_recv(
|
||||
buf: &[u8],
|
||||
inner_icmp_socket: &IcmpSocket,
|
||||
map: &HashMap<(Ipv4Addr, Identifier, SequenceNumber), Ipv4Addr>,
|
||||
no_tun: bool,
|
||||
network: &SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(ipv4) = Ipv4Packet::new(buf) else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(icmp) = IcmpPacket::new(ipv4.payload()) else {
|
||||
return Ok(());
|
||||
};
|
||||
if icmp.get_icmp_type() != IcmpTypes::EchoReply
|
||||
&& icmp.get_icmp_type() != IcmpTypes::EchoRequest
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let payload = icmp.payload();
|
||||
if payload.len() < 4 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut src = ipv4.get_source();
|
||||
let identifier = Identifier::new(u16::from_be_bytes([payload[0], payload[1]]));
|
||||
let sequence_number = SequenceNumber::new(u16::from_be_bytes([payload[2], payload[3]]));
|
||||
let Some(dst) = map.get(&(src, identifier, sequence_number)) else {
|
||||
return Ok(());
|
||||
};
|
||||
if no_tun && src == Ipv4Addr::LOCALHOST {
|
||||
src = network.ip().context("not ip")?;
|
||||
}
|
||||
|
||||
inner_icmp_socket
|
||||
.send_from_to(ipv4.payload(), src.into(), (*dst).into())
|
||||
.await
|
||||
.context("sending ICMPv4 failed")?;
|
||||
Ok(())
|
||||
}
|
||||
async fn inner_icmp_socket_recv(
|
||||
buf: &[u8],
|
||||
src: IpAddr,
|
||||
dst: IpAddr,
|
||||
tokio_icmp_socket: &UdpSocket,
|
||||
map: &mut HashMap<(Ipv4Addr, Identifier, SequenceNumber), Ipv4Addr>,
|
||||
no_tun: bool,
|
||||
network: &SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let (IpAddr::V4(src), IpAddr::V4(mut dst)) = (src, dst) else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(icmp) = IcmpPacket::new(buf) else {
|
||||
return Ok(());
|
||||
};
|
||||
if icmp.get_icmp_type() != IcmpTypes::EchoReply
|
||||
&& icmp.get_icmp_type() != IcmpTypes::EchoRequest
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let payload = icmp.payload();
|
||||
if payload.len() < 4 {
|
||||
return Ok(());
|
||||
}
|
||||
if no_tun && dst == network.ip().context("not ip")? {
|
||||
dst = Ipv4Addr::LOCALHOST;
|
||||
}
|
||||
|
||||
let identifier = Identifier::new(u16::from_be_bytes([payload[0], payload[1]]));
|
||||
let sequence_number = SequenceNumber::new(u16::from_be_bytes([payload[2], payload[3]]));
|
||||
map.insert((dst, identifier, sequence_number), src);
|
||||
tokio_icmp_socket
|
||||
.send_to(buf, SocketAddr::new(dst.into(), 0))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
use crate::context::{NetworkAddr, SharedNetworkAddr};
|
||||
use crate::nat::AllowSubnetExternalRoute;
|
||||
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::outbound::HybridOutbound;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use bytes::BytesMut;
|
||||
use pnet_packet::ip::IpNextHeaderProtocol;
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tcp_ip::{IpStackConfig, IpStackRecv, IpStackSend};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
mod icmp_nat;
|
||||
mod tcp_nat;
|
||||
mod udp_nat;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct InternalNatInbound {
|
||||
no_tun: bool,
|
||||
ip_stack_send: Arc<IpStackSend>,
|
||||
allow_subnet: AllowSubnetExternalRoute,
|
||||
network: SharedNetworkAddr,
|
||||
}
|
||||
impl InternalNatInbound {
|
||||
pub async fn create(
|
||||
task_group: &TaskGroup,
|
||||
mtu: u16,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
allow_subnet: AllowSubnetExternalRoute,
|
||||
network: SharedNetworkAddr,
|
||||
no_tun: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let ip_stack_config = IpStackConfig {
|
||||
mtu,
|
||||
..Default::default()
|
||||
};
|
||||
let (ip_stack, ip_stack_send, ip_stack_recv) = tcp_ip::ip_stack(ip_stack_config)?;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
icmp_nat::start_icmp_nat(task_group, &ip_stack, no_tun, network.clone()).await?;
|
||||
tcp_nat::start_tcp_nat(task_group, &ip_stack, no_tun, network.clone()).await?;
|
||||
udp_nat::start_udp_nat(task_group, &ip_stack).await?;
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = ip_stack_recv_task(ip_stack_recv, hybrid_outbound).await {
|
||||
log::error!("ip stack recv task error: {e:?}");
|
||||
}
|
||||
});
|
||||
Ok(Self {
|
||||
no_tun,
|
||||
ip_stack_send: Arc::new(ip_stack_send),
|
||||
allow_subnet,
|
||||
network,
|
||||
})
|
||||
}
|
||||
pub async fn send(&self, data: &[u8], net: &NetworkAddr) -> anyhow::Result<()> {
|
||||
if data[0] >> 4 != 4 {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(ipv4) = Ipv4Packet::new(data) else {
|
||||
return Ok(());
|
||||
};
|
||||
let dest = ipv4.get_destination();
|
||||
if net.network().contains(&dest)
|
||||
|| dest == net.broadcast
|
||||
|| dest.is_broadcast()
|
||||
|| dest.is_multicast()
|
||||
|| self.allow_subnet.allow(&dest)
|
||||
{
|
||||
self.ip_stack_send.send_ip_packet(data).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send_ipv4_payload(
|
||||
&self,
|
||||
protocol: IpNextHeaderProtocol,
|
||||
src_ip: Ipv4Addr,
|
||||
dest_ip: Ipv4Addr,
|
||||
payload: BytesMut,
|
||||
) -> anyhow::Result<()> {
|
||||
self.ip_stack_send
|
||||
.send_ipv4_payload(protocol, src_ip, dest_ip, payload)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn ip_stack_recv_task(
|
||||
mut ip_stack_recv: IpStackRecv,
|
||||
hybrid_outbound: HybridOutbound,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let mut bytes = TransmissionBytes::new_offset_zeroed(HEAD_LENGTH);
|
||||
let len = ip_stack_recv.recv(&mut bytes).await?;
|
||||
bytes.set_len(len)?;
|
||||
if let Err(e) = hybrid_outbound.ipv4_outbound_common(bytes).await {
|
||||
log::warn!("ip_stack_recv_task,{e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalNatInbound {
|
||||
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
|
||||
self.network
|
||||
.network()
|
||||
.map(|net| net.contains(ip))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn use_nat(&self, dst: &Ipv4Addr) -> bool {
|
||||
if self.no_tun {
|
||||
return self.allow_nat(dst);
|
||||
}
|
||||
if self.network_contains(dst) {
|
||||
return false;
|
||||
}
|
||||
self.allow_subnet.allow(dst)
|
||||
}
|
||||
pub fn no_tun(&self) -> bool {
|
||||
self.no_tun
|
||||
}
|
||||
pub fn allow_nat(&self, dst: &Ipv4Addr) -> bool {
|
||||
self.allow_subnet.allow(dst) || self.network_contains(dst)
|
||||
}
|
||||
pub async fn tcp_nat<R, W>(
|
||||
&self,
|
||||
recv_stream: R,
|
||||
send_stream: W,
|
||||
mut dest_ip: Ipv4Addr,
|
||||
dest_port: u16,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
if self.no_tun {
|
||||
let net = self.network.get().context("no network")?;
|
||||
if dest_ip == net.ip {
|
||||
dest_ip = Ipv4Addr::LOCALHOST;
|
||||
} else if net.network().contains(&dest_ip) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let dst = SocketAddr::new(dest_ip.into(), dest_port);
|
||||
tcp_nat::stream_nat(recv_stream, send_stream, dst).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PortMappingManager {
|
||||
no_tun: bool,
|
||||
allow_port_mapping: bool,
|
||||
network: SharedNetworkAddr,
|
||||
}
|
||||
|
||||
impl PortMappingManager {
|
||||
pub fn new(no_tun: bool, allow_port_mapping: bool, network: SharedNetworkAddr) -> Self {
|
||||
Self {
|
||||
no_tun,
|
||||
allow_port_mapping,
|
||||
network,
|
||||
}
|
||||
}
|
||||
pub async fn tcp_mapping<R, W>(
|
||||
&self,
|
||||
recv_stream: R,
|
||||
send_stream: W,
|
||||
dest: String,
|
||||
dest_port: u16,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
if !self.allow_port_mapping {
|
||||
log::debug!("port mapping not enabled");
|
||||
return Ok(());
|
||||
}
|
||||
if self.no_tun
|
||||
&& let Ok(dest_ip) = Ipv4Addr::from_str(&dest)
|
||||
{
|
||||
let net = self.network.get().context("no network")?;
|
||||
|
||||
if dest_ip == net.ip {
|
||||
let dst = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), dest_port);
|
||||
return tcp_nat::stream_nat(recv_stream, send_stream, dst).await;
|
||||
} else if net.network().contains(&dest_ip) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let dst = format!("{}:{}", dest, dest_port);
|
||||
tcp_nat::stream_nat(recv_stream, send_stream, dst).await
|
||||
}
|
||||
pub async fn udp_mapping<R, W>(
|
||||
&self,
|
||||
recv_stream: R,
|
||||
send_stream: W,
|
||||
dest: String,
|
||||
dest_port: u16,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
if !self.allow_port_mapping {
|
||||
log::debug!("port mapping not enabled");
|
||||
return Ok(());
|
||||
}
|
||||
if self.no_tun
|
||||
&& let Ok(dest_ip) = Ipv4Addr::from_str(&dest)
|
||||
{
|
||||
let net = self.network.get().context("no network")?;
|
||||
|
||||
if dest_ip == net.ip {
|
||||
let dst = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), dest_port);
|
||||
return udp_nat::stream_nat(recv_stream, send_stream, dst).await;
|
||||
} else if net.network().contains(&dest_ip) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let dst = format!("{}:{}", dest, dest_port);
|
||||
udp_nat::stream_nat(recv_stream, send_stream, dst).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use crate::context::SharedNetworkAddr;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use std::fmt::Debug;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use tcp_ip::IpStack;
|
||||
use tcp_ip::tcp::TcpListener;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpStream, ToSocketAddrs};
|
||||
|
||||
pub async fn start_tcp_nat(
|
||||
task_group: &TaskGroup,
|
||||
ip_stack: &IpStack,
|
||||
no_tun: bool,
|
||||
network: SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let tcp_listener = TcpListener::bind_all(ip_stack.clone()).await?;
|
||||
let group = task_group.clone();
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = listen_task(&group, tcp_listener, no_tun, network).await {
|
||||
log::error!("listen task error: {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn listen_task(
|
||||
task_group: &TaskGroup,
|
||||
mut tcp_listener: TcpListener,
|
||||
no_tun: bool,
|
||||
network: SharedNetworkAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let (stream, _addr) = tcp_listener.accept().await?;
|
||||
let mut local_addr = stream.local_addr()?;
|
||||
let peer_addr = stream.peer_addr()?;
|
||||
if no_tun {
|
||||
let IpAddr::V4(ip) = local_addr.ip() else {
|
||||
continue;
|
||||
};
|
||||
if ip == network.ip().context("not ip")? {
|
||||
// 无tun的情况下写入本机的则写到localhost
|
||||
local_addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||
}
|
||||
}
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = stream_task(stream, local_addr).await {
|
||||
log::error!("stream task Error: {:?},{peer_addr}->{local_addr}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_task(
|
||||
mut inner_stream: tcp_ip::tcp::TcpStream,
|
||||
addr: SocketAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut tokio_stream = TcpStream::connect(addr).await?;
|
||||
tokio::io::copy_bidirectional(&mut inner_stream, &mut tokio_stream).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn stream_nat<R, W, A: ToSocketAddrs + Debug>(
|
||||
mut recv_stream: R,
|
||||
mut send_stream: W,
|
||||
addr: A,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut tokio_stream = TcpStream::connect(&addr)
|
||||
.await
|
||||
.with_context(|| format!("error connecting to {:?}", addr))?;
|
||||
let (mut tcp_r, mut tcp_w) = tokio_stream.split();
|
||||
tokio::select! {
|
||||
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
|
||||
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tcp_ip::IpStack;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
|
||||
|
||||
struct NatEntry {
|
||||
socket: Arc<tokio::net::UdpSocket>,
|
||||
last_active: Instant,
|
||||
}
|
||||
|
||||
type NatTable = Arc<Mutex<HashMap<(SocketAddr, SocketAddr), NatEntry>>>;
|
||||
|
||||
const NAT_IDLE_TIMEOUT: Duration = Duration::from_secs(60 * 5);
|
||||
const NAT_GC_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
pub async fn start_udp_nat(task_group: &TaskGroup, ip_stack: &IpStack) -> anyhow::Result<()> {
|
||||
let inner_socket = tcp_ip::udp::UdpSocket::bind_all(ip_stack.clone()).await?;
|
||||
let inner_socket = Arc::new(inner_socket);
|
||||
let nat_table: NatTable = Arc::new(Mutex::new(HashMap::new()));
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let group = task_group.clone();
|
||||
let nat_table_clone = nat_table.clone();
|
||||
task_group.spawn(async move {
|
||||
loop {
|
||||
let (len, src, dst) = match inner_socket.recv_from_to(&mut buf).await {
|
||||
Ok(rs) => rs,
|
||||
Err(e) => {
|
||||
log::warn!("{e:?}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) =
|
||||
handle_outbound(&group, &inner_socket, &nat_table, src, dst, &buf[..len]).await
|
||||
{
|
||||
log::warn!("udp nat outbound error: {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
spawn_nat_gc(task_group, nat_table_clone);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_outbound(
|
||||
task_group: &TaskGroup,
|
||||
inner: &Arc<tcp_ip::udp::UdpSocket>,
|
||||
nat: &NatTable,
|
||||
src: SocketAddr,
|
||||
dst: SocketAddr,
|
||||
packet: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let key = (src, dst);
|
||||
|
||||
let socket = {
|
||||
let mut table = nat.lock().await;
|
||||
if let Some(entry) = table.get_mut(&key) {
|
||||
entry.last_active = Instant::now();
|
||||
entry.socket.clone()
|
||||
} else {
|
||||
// 创建真实 UDP socket
|
||||
let sock = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
|
||||
sock.connect(dst).await?;
|
||||
let sock = Arc::new(sock);
|
||||
table.insert(
|
||||
key,
|
||||
NatEntry {
|
||||
socket: sock.clone(),
|
||||
last_active: Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
// 启动反向转发
|
||||
spawn_inbound(
|
||||
task_group,
|
||||
inner.clone(),
|
||||
nat.clone(),
|
||||
src,
|
||||
dst,
|
||||
sock.clone(),
|
||||
);
|
||||
|
||||
sock
|
||||
}
|
||||
};
|
||||
|
||||
socket.send(packet).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_inbound(
|
||||
task_group: &TaskGroup,
|
||||
inner: Arc<tcp_ip::udp::UdpSocket>,
|
||||
nat: NatTable,
|
||||
src: SocketAddr,
|
||||
dst: SocketAddr,
|
||||
socket: Arc<tokio::net::UdpSocket>,
|
||||
) {
|
||||
task_group.spawn(async move {
|
||||
let mut buf = vec![0u8; 65536];
|
||||
|
||||
loop {
|
||||
let len = match socket.recv(&mut buf).await {
|
||||
Ok(n) => n,
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
// 反向写回 inner socket
|
||||
if inner.send_from_to(&buf[..len], dst, src).await.is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新活跃时间
|
||||
if let Some(entry) = nat.lock().await.get_mut(&(src, dst)) {
|
||||
entry.last_active = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
// 回收 NAT
|
||||
nat.lock().await.remove(&(src, dst));
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_nat_gc(task_group: &TaskGroup, nat: NatTable) {
|
||||
task_group.spawn(async move {
|
||||
let mut interval = tokio::time::interval(NAT_GC_INTERVAL);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let now = Instant::now();
|
||||
let mut table = nat.lock().await;
|
||||
|
||||
table.retain(|(src, dst), entry| {
|
||||
let alive = now.duration_since(entry.last_active) < NAT_IDLE_TIMEOUT;
|
||||
if !alive {
|
||||
log::debug!("udp nat expired: {} -> {}", src, dst);
|
||||
}
|
||||
alive
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn stream_nat<R, W, A: ToSocketAddrs + Debug>(
|
||||
recv_stream: R,
|
||||
send_stream: W,
|
||||
addr: A,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
let udp_socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
|
||||
udp_socket
|
||||
.connect(&addr)
|
||||
.await
|
||||
.with_context(|| format!("error connecting to {:?}", addr))?;
|
||||
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
|
||||
let mut framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
|
||||
let mut buf = vec![0u8; 65536];
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(buf) = framed_read.next() => {
|
||||
let buf = buf?;
|
||||
udp_socket.send(&buf).await?;
|
||||
},
|
||||
rs = udp_socket.recv(&mut buf) =>{
|
||||
let len = rs?;
|
||||
framed_write.send(Bytes::copy_from_slice(&buf[..len])).await?;
|
||||
},
|
||||
else => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use ipnet::Ipv4Net;
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod internal_nat;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NetInput {
|
||||
pub net: Ipv4Net,
|
||||
pub target_ip: Ipv4Addr,
|
||||
}
|
||||
impl FromStr for NetInput {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split(',').map(|x| x.trim()).collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("格式错误,应为 net,target_ip 例如: 192.168.0.0/24,10.26.0.2".into());
|
||||
}
|
||||
|
||||
let net = Ipv4Net::from_str(parts[0]).map_err(|e| format!("网络段格式错误: {}", e))?;
|
||||
|
||||
let target_ip =
|
||||
Ipv4Addr::from_str(parts[1]).map_err(|e| format!("目标 IP 格式错误: {}", e))?;
|
||||
|
||||
Ok(NetInput { net, target_ip })
|
||||
}
|
||||
}
|
||||
impl fmt::Display for NetInput {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{},{}", self.net, self.target_ip)
|
||||
}
|
||||
}
|
||||
impl Serialize for NetInput {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NetInput {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SubnetExternalRoute {
|
||||
route_table: Arc<Mutex<Vec<NetInput>>>,
|
||||
}
|
||||
impl SubnetExternalRoute {
|
||||
pub fn new(mut route_table: Vec<NetInput>) -> Self {
|
||||
route_table.sort_by_key(|r| std::cmp::Reverse(r.net.prefix_len()));
|
||||
SubnetExternalRoute {
|
||||
route_table: Arc::new(Mutex::new(route_table)),
|
||||
}
|
||||
}
|
||||
pub fn set_route_table(&self, mut route_table: Vec<NetInput>) {
|
||||
route_table.sort_by_key(|r| std::cmp::Reverse(r.net.prefix_len()));
|
||||
*self.route_table.lock() = route_table;
|
||||
}
|
||||
pub fn route(&self, ip: &Ipv4Addr) -> Option<Ipv4Addr> {
|
||||
let route_table = self.route_table.lock();
|
||||
if route_table.is_empty() {
|
||||
return None;
|
||||
}
|
||||
for net in route_table.iter() {
|
||||
if net.net.contains(ip) {
|
||||
return Some(net.target_ip);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn all_route(&self) -> Vec<NetInput> {
|
||||
self.route_table.lock().clone()
|
||||
}
|
||||
pub fn reset_route(&self, route_table: Vec<NetInput>) {
|
||||
*self.route_table.lock() = route_table;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AllowSubnetExternalRoute {
|
||||
route_table: Arc<Vec<Ipv4Net>>,
|
||||
}
|
||||
impl AllowSubnetExternalRoute {
|
||||
pub fn new(mut route_table: Vec<Ipv4Net>) -> Self {
|
||||
route_table.sort_by_key(|r| r.prefix_len());
|
||||
Self {
|
||||
route_table: Arc::new(route_table),
|
||||
}
|
||||
}
|
||||
pub fn allow(&self, ip: &Ipv4Addr) -> bool {
|
||||
if self.route_table.is_empty() {
|
||||
return false;
|
||||
}
|
||||
for net in self.route_table.iter() {
|
||||
if net.contains(ip) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use crate::enhanced_tunnel::quic_over::quic_client::QuicTunnelClient;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use pnet_packet::ip::{IpNextHeaderProtocol, IpNextHeaderProtocols};
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) mod tcp_port_mapping;
|
||||
pub(crate) mod udp_port_mapping;
|
||||
|
||||
pub(crate) async fn port_mapping_start(
|
||||
task_group: &TaskGroup,
|
||||
list: Vec<PortMapping>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
tcp_port_mapping::start(task_group, &list, quic_tunnel_client.clone()).await?;
|
||||
udp_port_mapping::start(task_group, &list, quic_tunnel_client).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PortMapping {
|
||||
pub protocol: IpNextHeaderProtocol,
|
||||
pub src_addr: SocketAddr,
|
||||
pub virtual_target_ip: Ipv4Addr,
|
||||
pub dst_host: String,
|
||||
pub dst_port: u16,
|
||||
}
|
||||
|
||||
impl fmt::Display for PortMapping {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}://{}-{}-{}:{}",
|
||||
protocol_to_str(self.protocol),
|
||||
self.src_addr,
|
||||
self.virtual_target_ip,
|
||||
self.dst_host,
|
||||
self.dst_port
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PortMapping {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (proto_str, rest) = s.split_once("://").ok_or("missing '://'")?;
|
||||
|
||||
let protocol =
|
||||
str_to_protocol(proto_str).ok_or_else(|| format!("unknown protocol: {}", proto_str))?;
|
||||
|
||||
let mut parts = rest.splitn(3, '-');
|
||||
|
||||
let src_addr = parts
|
||||
.next()
|
||||
.ok_or("missing src_addr")?
|
||||
.parse::<SocketAddr>()
|
||||
.map_err(|e| format!("invalid src_addr: {}", e))?;
|
||||
|
||||
let virtual_target_ip = parts
|
||||
.next()
|
||||
.ok_or("missing virtual_target_ip")?
|
||||
.parse::<Ipv4Addr>()
|
||||
.map_err(|e| format!("invalid virtual_target_ip: {}", e))?;
|
||||
|
||||
let dst = parts.next().ok_or("missing destination")?;
|
||||
|
||||
let (dst_host, dst_port) = dst.rsplit_once(':').ok_or("missing dst port")?;
|
||||
|
||||
let dst_port = dst_port
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("invalid dst_port: {}", e))?;
|
||||
if dst_port == 0 {
|
||||
return Err("invalid dst port: 0".to_string());
|
||||
}
|
||||
Ok(Self {
|
||||
protocol,
|
||||
src_addr,
|
||||
virtual_target_ip,
|
||||
dst_host: dst_host.to_string(),
|
||||
dst_port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn protocol_to_str(p: IpNextHeaderProtocol) -> &'static str {
|
||||
match p {
|
||||
IpNextHeaderProtocols::Tcp => "tcp",
|
||||
IpNextHeaderProtocols::Udp => "udp",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_protocol(s: &str) -> Option<IpNextHeaderProtocol> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"tcp" => Some(IpNextHeaderProtocols::Tcp),
|
||||
"udp" => Some(IpNextHeaderProtocols::Udp),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use crate::enhanced_tunnel::quic_over::quic_client::{QuicTunnelClient, send_handshake};
|
||||
use crate::port_mapping::PortMapping;
|
||||
use crate::protocol::client_message::{
|
||||
PortProxyHandshake, QuicProxyHandshake, quic_proxy_handshake,
|
||||
};
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use pnet_packet::ip::IpNextHeaderProtocols;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
pub async fn start(
|
||||
task_group: &TaskGroup,
|
||||
list: &Vec<PortMapping>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
for x in list {
|
||||
if x.protocol != IpNextHeaderProtocols::Tcp {
|
||||
continue;
|
||||
}
|
||||
log::info!("Starting TCP port mapping on {}", x);
|
||||
let listener = TcpListener::bind(x.src_addr)
|
||||
.await
|
||||
.with_context(|| format!("Tcp port mapping Failed to bind to {}", x.src_addr))?;
|
||||
let group = task_group.clone();
|
||||
let tunnel_client = quic_tunnel_client.clone();
|
||||
let mapping = x.clone();
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = listen(&group, listener, &mapping, tunnel_client).await {
|
||||
log::error!("listen {:?},mapping:{mapping}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn listen(
|
||||
task_group: &TaskGroup,
|
||||
listener: TcpListener,
|
||||
mapping: &PortMapping,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
let tunnel_client = quic_tunnel_client.clone();
|
||||
let target_ip = mapping.virtual_target_ip;
|
||||
let dst_host = mapping.dst_host.clone();
|
||||
let dst_port = mapping.dst_port;
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) =
|
||||
stream_copy(stream, addr, target_ip, dst_host, dst_port, tunnel_client).await
|
||||
{
|
||||
log::error!("TCP TCP Stream Error: {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_copy(
|
||||
mut tcp_stream: TcpStream,
|
||||
src: SocketAddr,
|
||||
target_ip: Ipv4Addr,
|
||||
dst_host: String,
|
||||
dst_port: u16,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let (mut send_stream, mut recv_stream) = quic_tunnel_client.open_bi(target_ip).await?;
|
||||
let handshake = QuicProxyHandshake {
|
||||
handshake: Some(quic_proxy_handshake::Handshake::TcpPortMapping(
|
||||
PortProxyHandshake {
|
||||
src_ip: src.ip().to_string(),
|
||||
src_port: src.port().into(),
|
||||
dst_host,
|
||||
dst_port: dst_port as _,
|
||||
},
|
||||
)),
|
||||
};
|
||||
send_handshake(&mut send_stream, handshake).await?;
|
||||
let (mut tcp_r, mut tcp_w) = tcp_stream.split();
|
||||
tokio::select! {
|
||||
_ = tokio::io::copy(&mut recv_stream, &mut tcp_w) => {},
|
||||
_ = tokio::io::copy(&mut tcp_r, &mut send_stream) => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::enhanced_tunnel::quic_over::quic_client::{QuicTunnelClient, send_handshake};
|
||||
use crate::port_mapping::PortMapping;
|
||||
use crate::protocol::client_message::{
|
||||
PortProxyHandshake, QuicProxyHandshake, quic_proxy_handshake,
|
||||
};
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use parking_lot::Mutex;
|
||||
use pnet_packet::ip::IpNextHeaderProtocols;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::mpsc::error::TrySendError;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
|
||||
|
||||
pub async fn start(
|
||||
task_group: &TaskGroup,
|
||||
list: &Vec<PortMapping>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
for x in list {
|
||||
if x.protocol != IpNextHeaderProtocols::Udp {
|
||||
continue;
|
||||
}
|
||||
log::info!("Starting UDP port mapping on {}", x);
|
||||
let udp = UdpSocket::bind(x.src_addr)
|
||||
.await
|
||||
.with_context(|| format!("Udp port mapping Failed to bind to {}", x.src_addr))?;
|
||||
let udp_socket = Arc::new(udp);
|
||||
let group = task_group.clone();
|
||||
let tunnel_client = quic_tunnel_client.clone();
|
||||
let mapping = x.clone();
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = recv(&group, udp_socket, &mapping, tunnel_client).await {
|
||||
log::error!("recv {:?},mapping:{mapping}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
task_group: &TaskGroup,
|
||||
udp_socket: Arc<UdpSocket>,
|
||||
mapping: &PortMapping,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let dest_map = Arc::new(Mutex::new(HashMap::<SocketAddr, Sender<Bytes>>::new()));
|
||||
loop {
|
||||
let (len, src) = udp_socket.recv_from(&mut buf).await?;
|
||||
let bytes = Bytes::copy_from_slice(&buf[..len]);
|
||||
|
||||
let tx = {
|
||||
let mut map = dest_map.lock();
|
||||
if let Some(tx) = map.get(&src) {
|
||||
tx.clone()
|
||||
} else {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(128);
|
||||
let udp_socket = udp_socket.clone();
|
||||
let virtual_target_ip = mapping.virtual_target_ip;
|
||||
let dst_host = mapping.dst_host.clone();
|
||||
let dst_port = mapping.dst_port;
|
||||
let tunnel_client = quic_tunnel_client.clone();
|
||||
task_group.spawn(async move {
|
||||
if let Err(e) = udp_mapping_handle(
|
||||
udp_socket,
|
||||
src,
|
||||
virtual_target_ip,
|
||||
dst_host,
|
||||
dst_port,
|
||||
rx,
|
||||
tunnel_client,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!("udp_mapping_handle {e:?},src:{src}");
|
||||
}
|
||||
});
|
||||
|
||||
map.insert(src, tx.clone());
|
||||
tx
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = tx.try_send(bytes) {
|
||||
match err {
|
||||
TrySendError::Full(_) => {}
|
||||
TrySendError::Closed(_) => {
|
||||
let mut map = dest_map.lock();
|
||||
map.remove(&src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn udp_mapping_handle(
|
||||
udp_socket: Arc<UdpSocket>,
|
||||
src: SocketAddr,
|
||||
target_ip: Ipv4Addr,
|
||||
dst_host: String,
|
||||
dst_port: u16,
|
||||
mut rx: tokio::sync::mpsc::Receiver<Bytes>,
|
||||
quic_tunnel_client: QuicTunnelClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let (mut send_stream, recv_stream) = quic_tunnel_client.open_bi(target_ip).await?;
|
||||
|
||||
let handshake = QuicProxyHandshake {
|
||||
handshake: Some(quic_proxy_handshake::Handshake::UdpPortMapping(
|
||||
PortProxyHandshake {
|
||||
src_ip: src.ip().to_string(),
|
||||
src_port: src.port().into(),
|
||||
dst_host,
|
||||
dst_port: dst_port as _,
|
||||
},
|
||||
)),
|
||||
};
|
||||
send_handshake(&mut send_stream, handshake).await?;
|
||||
let mut framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
|
||||
let mut framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(buf) = framed_read.next()=>{
|
||||
let buf = buf?;
|
||||
udp_socket.send_to(&buf, src).await?;
|
||||
},
|
||||
Some(buf) = rx.recv()=>{
|
||||
framed_write.send(buf).await?
|
||||
},
|
||||
_ = tokio::time::sleep(Duration::from_secs(60)) =>{
|
||||
break;
|
||||
},
|
||||
else => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.client.rs"));
|
||||
}
|
||||
|
||||
use anyhow::bail;
|
||||
use bytes::BytesMut;
|
||||
use prost::Message;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use crate::protocol::ProtoToBytesMut;
|
||||
pub use proto::*;
|
||||
|
||||
pub fn encode_nat_info(nat_info: &rust_p2p_core::nat::NatInfo) -> proto::NatInfo {
|
||||
let nat_type = match nat_info.nat_type {
|
||||
rust_p2p_core::nat::NatType::Cone => proto::NatType::Cone,
|
||||
rust_p2p_core::nat::NatType::Symmetric => proto::NatType::Symmetric,
|
||||
};
|
||||
|
||||
proto::NatInfo {
|
||||
nat_type: nat_type.into(),
|
||||
public_ips: nat_info.public_ips.iter().map(|v| (*v).into()).collect(),
|
||||
public_udp_ports: nat_info
|
||||
.public_udp_ports
|
||||
.iter()
|
||||
.map(|v| (*v).into())
|
||||
.collect(),
|
||||
public_port_range: nat_info.public_port_range.into(),
|
||||
local_ipv4s: nat_info.local_ipv4s.iter().map(|v| (*v).into()).collect(),
|
||||
ipv6: nat_info.ipv6.map(|v| v.octets().to_vec()),
|
||||
local_udp_ports: nat_info
|
||||
.local_udp_ports
|
||||
.iter()
|
||||
.map(|v| (*v).into())
|
||||
.collect(),
|
||||
local_tcp_port: nat_info.local_tcp_port.into(),
|
||||
public_tcp_port: nat_info.public_tcp_port.into(),
|
||||
}
|
||||
}
|
||||
pub fn decode_nat_info(msg: proto::NatInfo) -> anyhow::Result<rust_p2p_core::nat::NatInfo> {
|
||||
let nat_type = match msg.nat_type() {
|
||||
proto::NatType::Cone => rust_p2p_core::nat::NatType::Cone,
|
||||
proto::NatType::Symmetric => rust_p2p_core::nat::NatType::Symmetric,
|
||||
};
|
||||
let ipv6: Option<[u8; 16]> = msg.ipv6.and_then(|v| v.as_slice().try_into().ok());
|
||||
|
||||
// Validate all ports fit in u16
|
||||
let validate_port = |p: u32| -> anyhow::Result<u16> {
|
||||
u16::try_from(p).map_err(|_| anyhow::anyhow!("invalid port number: {}", p))
|
||||
};
|
||||
|
||||
let public_udp_ports: Result<Vec<_>, _> = msg
|
||||
.public_udp_ports
|
||||
.into_iter()
|
||||
.map(validate_port)
|
||||
.collect();
|
||||
let local_udp_ports: Result<Vec<_>, _> =
|
||||
msg.local_udp_ports.into_iter().map(validate_port).collect();
|
||||
|
||||
Ok(rust_p2p_core::nat::NatInfo {
|
||||
nat_type,
|
||||
public_ips: msg.public_ips.into_iter().map(|v| v.into()).collect(),
|
||||
public_udp_ports: public_udp_ports?,
|
||||
mapping_tcp_addr: vec![],
|
||||
mapping_udp_addr: vec![],
|
||||
public_port_range: validate_port(msg.public_port_range)?,
|
||||
local_ipv4: msg
|
||||
.local_ipv4s
|
||||
.first()
|
||||
.map(|v| (*v).into())
|
||||
.unwrap_or(Ipv4Addr::UNSPECIFIED),
|
||||
local_ipv4s: msg.local_ipv4s.into_iter().map(|v| v.into()).collect(),
|
||||
ipv6: ipv6.map(Ipv6Addr::from),
|
||||
local_udp_ports: local_udp_ports?,
|
||||
local_tcp_port: validate_port(msg.local_tcp_port)?,
|
||||
public_tcp_port: validate_port(msg.public_tcp_port)?,
|
||||
})
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PunchInfo {
|
||||
pub nat_info: rust_p2p_core::nat::NatInfo,
|
||||
}
|
||||
|
||||
impl PunchInfo {
|
||||
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
|
||||
let msg = proto::PunchInfo::decode(buf)?;
|
||||
let Some(nat_info) = msg.nat_info else {
|
||||
bail!("Punched info decode failed.");
|
||||
};
|
||||
let nat_info = decode_nat_info(nat_info)?;
|
||||
Ok(Self { nat_info })
|
||||
}
|
||||
pub fn encode(&self) -> BytesMut {
|
||||
let message = proto::PunchInfo {
|
||||
nat_info: Some(encode_nat_info(&self.nat_info)),
|
||||
};
|
||||
message.encode_bytes_mut()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use crate::protocol::ProtoToBytesMut;
|
||||
pub(crate) use crate::protocol::control_message::proto::SelectiveBroadcast;
|
||||
use crate::protocol::control_message::proto::request_message::RequestPayload;
|
||||
use crate::protocol::control_message::proto::response_message::ResponsePayload;
|
||||
use anyhow::bail;
|
||||
use bytes::BytesMut;
|
||||
use prost::Message;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.control_message.rs"));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub enum RegistrationMode {
|
||||
#[default]
|
||||
Normal = 0,
|
||||
PreRegister = 1,
|
||||
}
|
||||
|
||||
impl From<RegistrationMode> for proto::RegistrationMode {
|
||||
fn from(mode: RegistrationMode) -> Self {
|
||||
match mode {
|
||||
RegistrationMode::Normal => proto::RegistrationMode::Normal,
|
||||
RegistrationMode::PreRegister => proto::RegistrationMode::PreRegister,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<proto::RegistrationMode> for RegistrationMode {
|
||||
fn from(mode: proto::RegistrationMode) -> Self {
|
||||
match mode {
|
||||
proto::RegistrationMode::Normal => RegistrationMode::Normal,
|
||||
proto::RegistrationMode::PreRegister => RegistrationMode::PreRegister,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) struct RegRequestMsg {
|
||||
pub network_code: String,
|
||||
pub device_id: String,
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub key_sign: Option<String>,
|
||||
pub ip_variable: bool,
|
||||
pub server_id: u32,
|
||||
pub registration_mode: RegistrationMode,
|
||||
}
|
||||
impl RegRequestMsg {
|
||||
// pub fn check(&self) -> anyhow::Result<()> {
|
||||
// if self.network_code.is_empty() {
|
||||
// return Err(anyhow!("network_code cannot be empty"));
|
||||
// }
|
||||
// if self.network_code.len() > MAX_NETWORK_CODE_LEN {
|
||||
// return Err(anyhow!(
|
||||
// "network_code length exceeds {} characters (current: {})",
|
||||
// MAX_NETWORK_CODE_LEN,
|
||||
// self.network_code.len()
|
||||
// ));
|
||||
// }
|
||||
// if self.device_id.is_empty() {
|
||||
// return Err(anyhow!("device_id cannot be empty"));
|
||||
// }
|
||||
// if self.device_id.len() > MAX_DEVICE_ID_LEN {
|
||||
// return Err(anyhow!(
|
||||
// "device_id length exceeds {} characters (current: {})",
|
||||
// MAX_DEVICE_ID_LEN,
|
||||
// self.device_id.len()
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// if self.name.len() > MAX_NAME_LEN {
|
||||
// return Err(anyhow!(
|
||||
// "name length exceeds {} characters (current: {})",
|
||||
// MAX_NAME_LEN,
|
||||
// self.name.len()
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// if self.version.len() > MAX_VERSION_LEN {
|
||||
// return Err(anyhow!(
|
||||
// "version length exceeds {} characters (current: {})",
|
||||
// MAX_VERSION_LEN,
|
||||
// self.version.len()
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
// pub fn from(msg: proto::RegRequestMsg) -> anyhow::Result<Self> {
|
||||
// Ok(Self {
|
||||
// network_code: msg.network_code,
|
||||
// device_id: msg.device_id,
|
||||
// ip: msg.ip.map(|ip| ip.into()),
|
||||
// name: msg.name,
|
||||
// version: msg.version,
|
||||
// key_sign: msg.key_sign,
|
||||
// ip_variable: msg.ip_variable,
|
||||
// server_id: msg.server_id,
|
||||
// })
|
||||
// }
|
||||
pub fn to(self) -> proto::RegRequestMsg {
|
||||
proto::RegRequestMsg {
|
||||
network_code: self.network_code,
|
||||
device_id: self.device_id,
|
||||
ip: self.ip.map(|ip| ip.into()),
|
||||
name: self.name,
|
||||
version: self.version,
|
||||
key_sign: self.key_sign,
|
||||
ip_variable: self.ip_variable,
|
||||
server_id: self.server_id,
|
||||
registration_mode: proto::RegistrationMode::from(self.registration_mode).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RegResponseMsg {
|
||||
pub ip: Ipv4Addr,
|
||||
pub prefix_len: u8,
|
||||
pub gateway: Ipv4Addr,
|
||||
pub server_version: String,
|
||||
}
|
||||
impl RegResponseMsg {
|
||||
pub fn from(msg: proto::RegResponseMsg) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
ip: msg.ip.into(),
|
||||
prefix_len: (msg.prefix_len & 0xFF) as u8,
|
||||
gateway: msg.gateway.into(),
|
||||
server_version: msg.server_version,
|
||||
})
|
||||
}
|
||||
pub fn to(self) -> proto::RegResponseMsg {
|
||||
proto::RegResponseMsg {
|
||||
ip: self.ip.into(),
|
||||
prefix_len: self.prefix_len as _,
|
||||
gateway: self.gateway.into(),
|
||||
server_version: self.server_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ErrorResponseMsg {
|
||||
pub code: u32,
|
||||
pub message: String,
|
||||
}
|
||||
impl ErrorResponseMsg {
|
||||
pub fn from(msg: proto::ErrorResponseMsg) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
code: msg.code,
|
||||
message: msg.message,
|
||||
})
|
||||
}
|
||||
pub fn to(self) -> proto::ErrorResponseMsg {
|
||||
proto::ErrorResponseMsg {
|
||||
code: self.code,
|
||||
message: self.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ConfirmRegResponseMsg {
|
||||
pub success: bool,
|
||||
}
|
||||
impl ConfirmRegResponseMsg {
|
||||
pub fn from(msg: proto::ConfirmRegResponseMsg) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
success: msg.success,
|
||||
})
|
||||
}
|
||||
pub fn to(self) -> proto::ConfirmRegResponseMsg {
|
||||
proto::ConfirmRegResponseMsg {
|
||||
success: self.success,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) enum RequestMessage {
|
||||
Reg(RegRequestMsg),
|
||||
ConfirmReg,
|
||||
}
|
||||
impl RequestMessage {
|
||||
pub fn encode(self) -> BytesMut {
|
||||
let request_payload = match self {
|
||||
RequestMessage::Reg(reg) => RequestPayload::Reg(reg.to()),
|
||||
RequestMessage::ConfirmReg => RequestPayload::ConfirmReg(proto::ConfirmRegMsg {}),
|
||||
};
|
||||
proto::RequestMessage {
|
||||
request_payload: Some(request_payload),
|
||||
}
|
||||
.encode_bytes_mut()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ResponseMessage {
|
||||
Reg(RegResponseMsg),
|
||||
Error(ErrorResponseMsg),
|
||||
ConfirmReg(ConfirmRegResponseMsg),
|
||||
}
|
||||
impl ResponseMessage {
|
||||
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
|
||||
let msg = proto::ResponseMessage::decode(buf)?;
|
||||
let Some(payload) = msg.response_payload else {
|
||||
bail!("unsupported")
|
||||
};
|
||||
match payload {
|
||||
ResponsePayload::Reg(reg) => Ok(ResponseMessage::Reg(RegResponseMsg::from(reg)?)),
|
||||
ResponsePayload::Error(e) => Ok(ResponseMessage::Error(ErrorResponseMsg::from(e)?)),
|
||||
ResponsePayload::ConfirmReg(c) => {
|
||||
Ok(ResponseMessage::ConfirmReg(ConfirmRegResponseMsg::from(c)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn encode(self) -> BytesMut {
|
||||
let response_payload = match self {
|
||||
ResponseMessage::Reg(reg) => ResponsePayload::Reg(reg.to()),
|
||||
ResponseMessage::Error(e) => ResponsePayload::Error(e.to()),
|
||||
ResponseMessage::ConfirmReg(c) => ResponsePayload::ConfirmReg(c.to()),
|
||||
};
|
||||
proto::ResponseMessage {
|
||||
response_payload: Some(response_payload),
|
||||
}
|
||||
.encode_bytes_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectiveBroadcast {
|
||||
pub fn new(ips: &[Ipv4Addr], data: Vec<u8>) -> Self {
|
||||
SelectiveBroadcast {
|
||||
ips: ips.iter().map(|v| (*v).into()).collect(),
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientSimpleInfo {
|
||||
pub ip: Ipv4Addr,
|
||||
pub online: bool,
|
||||
}
|
||||
impl ClientSimpleInfo {
|
||||
pub fn from(msg: proto::ClientSimpleInfo) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
ip: msg.ip.into(),
|
||||
online: msg.online,
|
||||
})
|
||||
}
|
||||
pub fn to(self) -> proto::ClientSimpleInfo {
|
||||
proto::ClientSimpleInfo {
|
||||
ip: self.ip.into(),
|
||||
online: self.online,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSimpleInfoList {
|
||||
pub data_version: u64,
|
||||
pub list: Vec<ClientSimpleInfo>,
|
||||
pub is_all: bool,
|
||||
pub time: i64,
|
||||
}
|
||||
impl ClientSimpleInfoList {
|
||||
pub fn from_slice(buf: &[u8]) -> anyhow::Result<Self> {
|
||||
let msg = proto::ClientSimpleInfoList::decode(buf)?;
|
||||
let mut list = Vec::with_capacity(msg.list.len());
|
||||
for x in msg.list {
|
||||
list.push(ClientSimpleInfo::from(x)?);
|
||||
}
|
||||
Ok(Self {
|
||||
data_version: msg.data_version,
|
||||
list,
|
||||
is_all: msg.is_all,
|
||||
time: msg.time,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
0 15 31
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 1 | msg_type(7) |max ttl(4) |curr ttl(4)| C | G | R | reserve(13) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| seq(32) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| src ID(32) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| dest ID(32) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| payload(n) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
#![allow(dead_code)]
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use std::io;
|
||||
use zerocopy::byteorder::{NetworkEndian, U32};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned};
|
||||
|
||||
#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
|
||||
#[repr(C)]
|
||||
pub struct NetHeader {
|
||||
/// Byte 0: bit7 = 1, bit0..6 = msg_type
|
||||
pub type_byte: u8,
|
||||
/// Byte 1: high 4 = max ttl, low 4 = curr ttl
|
||||
pub ttl_byte: u8,
|
||||
/// Byte 2: C(0x80) | G(0x40) | reserve
|
||||
pub flags_byte: u8,
|
||||
/// Byte 3: reserve
|
||||
pub _reserved: u8,
|
||||
|
||||
pub seq: U32<NetworkEndian>,
|
||||
pub src_id: U32<NetworkEndian>,
|
||||
pub dest_id: U32<NetworkEndian>,
|
||||
}
|
||||
const COMPRESSED: u8 = 0x80;
|
||||
const GATEWAY: u8 = 0x40;
|
||||
const FEC: u8 = 0x20;
|
||||
impl NetHeader {
|
||||
#[inline]
|
||||
pub fn msg_type(&self) -> u8 {
|
||||
self.type_byte & 0x7F
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_msg_type(&mut self, msg_type: u8) {
|
||||
self.type_byte = (msg_type & 0x7F) | 0x80;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_ttl(&self) -> u8 {
|
||||
self.ttl_byte >> 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn curr_ttl(&self) -> u8 {
|
||||
self.ttl_byte & 0x0F
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ttl(&mut self, max: u8, curr: u8) {
|
||||
self.ttl_byte = (max << 4) | (curr & 0x0F);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decr_ttl(&mut self) {
|
||||
let curr = self.curr_ttl();
|
||||
if curr == 0 {
|
||||
return;
|
||||
}
|
||||
self.ttl_byte = (self.ttl_byte & 0xF0) | (curr - 1);
|
||||
}
|
||||
|
||||
fn set_flag(&mut self, mask: u8, val: bool) {
|
||||
if val {
|
||||
self.flags_byte |= mask;
|
||||
} else {
|
||||
self.flags_byte &= !mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum MsgType {
|
||||
Turn = 1,
|
||||
Broadcast = 2,
|
||||
ExcludeBroadcast = 3,
|
||||
TargetBroadcast = 4,
|
||||
|
||||
Ping = 5,
|
||||
Pong = 6,
|
||||
PingTurn = 7,
|
||||
PongTurn = 8,
|
||||
|
||||
PunchStart1 = 9,
|
||||
PunchStart2 = 10,
|
||||
PunchReq = 11,
|
||||
PunchRes = 12,
|
||||
|
||||
PushClientIps = 13,
|
||||
|
||||
RpcReq = 14,
|
||||
RpcRes = 15,
|
||||
|
||||
Quic = 17,
|
||||
}
|
||||
impl From<MsgType> for u8 {
|
||||
fn from(val: MsgType) -> Self {
|
||||
val as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for MsgType {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
let val = match value {
|
||||
1 => MsgType::Turn,
|
||||
2 => MsgType::Broadcast,
|
||||
3 => MsgType::ExcludeBroadcast,
|
||||
4 => MsgType::TargetBroadcast,
|
||||
|
||||
5 => MsgType::Ping,
|
||||
6 => MsgType::Pong,
|
||||
7 => MsgType::PingTurn,
|
||||
8 => MsgType::PongTurn,
|
||||
|
||||
9 => MsgType::PunchStart1,
|
||||
10 => MsgType::PunchStart2,
|
||||
11 => MsgType::PunchReq,
|
||||
12 => MsgType::PunchRes,
|
||||
|
||||
13 => MsgType::PushClientIps,
|
||||
|
||||
14 => MsgType::RpcReq,
|
||||
15 => MsgType::RpcRes,
|
||||
|
||||
17 => MsgType::Quic,
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("invalid msg type:{value}"),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
pub const HEAD_LENGTH: usize = std::mem::size_of::<NetHeader>();
|
||||
|
||||
pub struct NetPacket<B> {
|
||||
buffer: B,
|
||||
}
|
||||
impl<B: AsRef<[u8]>> NetPacket<B> {
|
||||
pub fn new(buffer: B) -> io::Result<NetPacket<B>> {
|
||||
if buffer.as_ref().len() < HEAD_LENGTH {
|
||||
return Err(io::ErrorKind::InvalidInput.into());
|
||||
}
|
||||
Ok(NetPacket { buffer })
|
||||
}
|
||||
fn header(&self) -> Ref<&[u8], NetHeader> {
|
||||
// Safe: NetHeader is Unaligned and length is validated in new()
|
||||
let (header, _) = Ref::<&[u8], NetHeader>::from_prefix(self.buffer.as_ref()).unwrap();
|
||||
header
|
||||
}
|
||||
pub fn buffer(&self) -> &[u8] {
|
||||
self.buffer.as_ref()
|
||||
}
|
||||
pub fn into_buffer(self) -> B {
|
||||
self.buffer
|
||||
}
|
||||
pub fn source_buf(&self) -> &B {
|
||||
&self.buffer
|
||||
}
|
||||
pub fn msg_type(&self) -> io::Result<MsgType> {
|
||||
self.header().msg_type().try_into()
|
||||
}
|
||||
pub fn max_ttl(&self) -> u8 {
|
||||
self.header().max_ttl()
|
||||
}
|
||||
pub fn ttl(&self) -> u8 {
|
||||
self.header().curr_ttl()
|
||||
}
|
||||
|
||||
pub fn seq(&self) -> u32 {
|
||||
self.header().seq.get()
|
||||
}
|
||||
|
||||
pub fn src_id(&self) -> u32 {
|
||||
self.header().src_id.get()
|
||||
}
|
||||
|
||||
pub fn dest_id(&self) -> u32 {
|
||||
self.header().dest_id.get()
|
||||
}
|
||||
pub fn is_compressed(&self) -> bool {
|
||||
(self.header().flags_byte & COMPRESSED) != 0
|
||||
}
|
||||
pub fn is_gateway(&self) -> bool {
|
||||
(self.header().flags_byte & GATEWAY) != 0
|
||||
}
|
||||
pub fn is_fec(&self) -> bool {
|
||||
(self.header().flags_byte & FEC) != 0
|
||||
}
|
||||
pub fn head(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[..HEAD_LENGTH]
|
||||
}
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer.as_ref()[HEAD_LENGTH..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: AsRef<[u8]> + AsMut<[u8]>> NetPacket<B> {
|
||||
fn header_mut(&mut self) -> Ref<&mut [u8], NetHeader> {
|
||||
// Safe: NetHeader is Unaligned and length is validated in new()
|
||||
let (header, _) = Ref::<&mut [u8], NetHeader>::from_prefix(self.buffer.as_mut()).unwrap();
|
||||
header
|
||||
}
|
||||
|
||||
pub fn set_msg_type(&mut self, msg_type: MsgType) {
|
||||
self.header_mut().set_msg_type(msg_type.into());
|
||||
}
|
||||
|
||||
pub fn decr_ttl(&mut self){
|
||||
self.header_mut().decr_ttl()
|
||||
}
|
||||
|
||||
pub fn set_ttl(&mut self, ttl: u8) {
|
||||
self.header_mut().set_ttl(ttl, ttl);
|
||||
}
|
||||
|
||||
pub fn set_seq(&mut self, seq: u32) {
|
||||
self.header_mut().seq.set(seq);
|
||||
}
|
||||
|
||||
pub fn set_src_id(&mut self, id: u32) {
|
||||
self.header_mut().src_id.set(id);
|
||||
}
|
||||
|
||||
pub fn set_dest_id(&mut self, id: u32) {
|
||||
self.header_mut().dest_id.set(id);
|
||||
}
|
||||
|
||||
pub fn set_compressed_flag(&mut self, compressed: bool) {
|
||||
self.header_mut().set_flag(COMPRESSED, compressed);
|
||||
}
|
||||
pub fn set_gateway_flag(&mut self, gateway: bool) {
|
||||
self.header_mut().set_flag(GATEWAY, gateway);
|
||||
}
|
||||
pub fn set_fec_flag(&mut self, fec: bool) {
|
||||
self.header_mut().set_flag(FEC, fec);
|
||||
}
|
||||
|
||||
pub fn set_payload(&mut self, data: &[u8]) -> io::Result<()> {
|
||||
let buf = self.buffer.as_mut();
|
||||
if buf.len() < HEAD_LENGTH + data.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid message length",
|
||||
));
|
||||
}
|
||||
buf[HEAD_LENGTH..HEAD_LENGTH + data.len()].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
pub fn head_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer.as_mut()[..HEAD_LENGTH]
|
||||
}
|
||||
pub fn payload_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer.as_mut()[HEAD_LENGTH..]
|
||||
}
|
||||
pub fn source_buf_mut(&mut self) -> &mut B {
|
||||
&mut self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NetPacket<Bytes> {
|
||||
fn clone(&self) -> Self {
|
||||
NetPacket {
|
||||
buffer: self.buffer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetPacket<BytesMut> {
|
||||
pub fn into_bytes(self) -> NetPacket<Bytes> {
|
||||
NetPacket {
|
||||
buffer: self.buffer.freeze(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetPacket<TransmissionBytes> {
|
||||
pub fn into_bytes(self) -> NetPacket<Bytes> {
|
||||
NetPacket {
|
||||
buffer: self.buffer.into_bytes().freeze(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use bytes::BytesMut;
|
||||
use prost::Message;
|
||||
|
||||
pub(crate) mod client_message;
|
||||
pub mod control_message;
|
||||
pub(crate) mod ip_packet_protocol;
|
||||
pub(crate) mod rpc_message;
|
||||
pub(crate) mod transmission;
|
||||
|
||||
pub trait ProtoToBytesMut: Message {
|
||||
fn encode_bytes_mut(&self) -> BytesMut
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut bytes_mut = BytesMut::with_capacity(self.encoded_len());
|
||||
self.encode_raw(&mut bytes_mut);
|
||||
bytes_mut
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message> ProtoToBytesMut for T {}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.rpc.rs"));
|
||||
}
|
||||
pub use proto::*;
|
||||
@@ -0,0 +1,266 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::io;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
const DEFAULT_BUF_SIZE: usize = 2048;
|
||||
#[derive(Clone)]
|
||||
pub struct TransmissionBytes {
|
||||
buf: BytesMut,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
impl From<BytesMut> for TransmissionBytes {
|
||||
fn from(buf: BytesMut) -> TransmissionBytes {
|
||||
let end = buf.len();
|
||||
Self { buf, start: 0, end }
|
||||
}
|
||||
}
|
||||
impl From<Bytes> for TransmissionBytes {
|
||||
fn from(buf: Bytes) -> TransmissionBytes {
|
||||
let end = buf.len();
|
||||
Self {
|
||||
buf: BytesMut::from(buf),
|
||||
start: 0,
|
||||
end,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&[u8]> for TransmissionBytes {
|
||||
fn from(buf: &[u8]) -> TransmissionBytes {
|
||||
let end = buf.len();
|
||||
Self {
|
||||
buf: BytesMut::from(buf),
|
||||
start: 0,
|
||||
end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmissionBytes {
|
||||
pub fn new_offset(start: usize) -> Self {
|
||||
TransmissionBytes {
|
||||
buf: BytesMut::zeroed(DEFAULT_BUF_SIZE),
|
||||
start,
|
||||
end: start,
|
||||
}
|
||||
}
|
||||
pub fn new_offset_zeroed(start: usize) -> Self {
|
||||
TransmissionBytes {
|
||||
buf: BytesMut::zeroed(DEFAULT_BUF_SIZE),
|
||||
start,
|
||||
end: DEFAULT_BUF_SIZE,
|
||||
}
|
||||
}
|
||||
pub fn zeroed(cap: usize) -> Self {
|
||||
TransmissionBytes {
|
||||
buf: BytesMut::zeroed(cap),
|
||||
start: 0,
|
||||
end: cap,
|
||||
}
|
||||
}
|
||||
pub fn zeroed_size(size: usize, reserve: usize) -> Self {
|
||||
TransmissionBytes {
|
||||
buf: BytesMut::zeroed(size + reserve),
|
||||
start: 0,
|
||||
end: size,
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_capacity(head_room: usize, capacity: usize) -> Self {
|
||||
TransmissionBytes {
|
||||
buf: BytesMut::zeroed(capacity),
|
||||
start: head_room,
|
||||
end: head_room,
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.buf.capacity()
|
||||
}
|
||||
/// 头部可用空间(可向前扩展的字节数)
|
||||
#[inline]
|
||||
pub fn head_room(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
/// 尾部可用空间(可向后扩展的字节数)
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn tail_room(&self) -> usize {
|
||||
self.buf.capacity() - self.end
|
||||
}
|
||||
#[inline]
|
||||
fn as_slice(&self) -> &[u8] {
|
||||
&self.buf[self.start..self.end]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_slice_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buf[self.start..self.end]
|
||||
}
|
||||
pub fn put(&mut self, data: &[u8]) -> io::Result<()> {
|
||||
let need = data.len();
|
||||
let free = self.buf.capacity() - self.end;
|
||||
|
||||
if need > free {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("data too large:need={need},free={free}"),
|
||||
));
|
||||
}
|
||||
|
||||
self.buf[self.end..self.end + need].copy_from_slice(data);
|
||||
self.end += need;
|
||||
Ok(())
|
||||
}
|
||||
pub fn retreat_head(&mut self, len: usize) -> io::Result<()> {
|
||||
if len > self.head_room() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"retreat_head beyond start: len={len}, head_room={}",
|
||||
self.head_room()
|
||||
),
|
||||
));
|
||||
}
|
||||
self.start -= len;
|
||||
Ok(())
|
||||
}
|
||||
pub fn advance_head(&mut self, len: usize) -> io::Result<()> {
|
||||
if len > self.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"advance_head beyond end: len={len}, data_len={}",
|
||||
self.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
self.start += len;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_len(&mut self, new_len: usize) -> io::Result<()> {
|
||||
let new_end = self.start + new_len;
|
||||
if new_end > self.buf.capacity() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"set_len exceeds capacity: new_len={new_len}, max={}",
|
||||
self.buf.capacity() - self.start
|
||||
),
|
||||
));
|
||||
}
|
||||
self.end = new_end;
|
||||
Ok(())
|
||||
}
|
||||
pub fn resize(&mut self, new_len: usize, value: u8) {
|
||||
let new_end = self.start + new_len;
|
||||
self.buf.resize(new_end, value);
|
||||
self.end = new_end;
|
||||
}
|
||||
pub fn extend_end(&mut self, n: usize) {
|
||||
if self.end + n > self.buf.len() {
|
||||
self.buf.resize(self.end + n, 0);
|
||||
}
|
||||
self.end += n;
|
||||
}
|
||||
pub fn shrink_end(&mut self, n: usize) {
|
||||
if n >= self.end - self.start {
|
||||
self.end = self.start;
|
||||
} else {
|
||||
self.end -= n;
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn clear(&mut self) {
|
||||
self.start = 0;
|
||||
self.end = 0;
|
||||
}
|
||||
pub fn into_bytes(mut self) -> BytesMut {
|
||||
self.buf.truncate(self.end);
|
||||
if self.start > 0 {
|
||||
self.buf.advance(self.start);
|
||||
}
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for TransmissionBytes {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TransmissionBytes {
|
||||
type Target = [u8];
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for TransmissionBytes {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.as_slice_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TransmissionBytes {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<[u8]> for TransmissionBytes {
|
||||
fn borrow(&self) -> &[u8] {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowMut<[u8]> for TransmissionBytes {
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ShrinkEnd {
|
||||
fn shrink_end(&mut self, n: usize);
|
||||
}
|
||||
pub trait ExtendEnd {
|
||||
fn extend_end(&mut self, n: usize);
|
||||
}
|
||||
|
||||
impl ShrinkEnd for TransmissionBytes {
|
||||
fn shrink_end(&mut self, n: usize) {
|
||||
self.shrink_end(n);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendEnd for TransmissionBytes {
|
||||
fn extend_end(&mut self, n: usize) {
|
||||
self.extend_end(n);
|
||||
}
|
||||
}
|
||||
|
||||
impl ShrinkEnd for &mut TransmissionBytes {
|
||||
fn shrink_end(&mut self, n: usize) {
|
||||
TransmissionBytes::shrink_end(self, n);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendEnd for &mut TransmissionBytes {
|
||||
fn extend_end(&mut self, n: usize) {
|
||||
TransmissionBytes::extend_end(self, n);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use anyhow::{Context, Result};
|
||||
use rcgen::{CertificateParams, DnType, KeyPair, PKCS_ED25519, SerialNumber};
|
||||
use rustls::pki_types;
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||
use sha2::{Digest, Sha256};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
const ED25519_PKCS8_V1_PREFIX: [u8; 16] = [
|
||||
0x30, 0x2e, // Sequence (len 46)
|
||||
0x02, 0x01, 0x00, // Version 0
|
||||
0x30, 0x05, // Sequence (len 5)
|
||||
0x06, 0x03, 0x2b, 0x65, 0x70, // OID: 1.3.101.112 (Ed25519)
|
||||
0x04, 0x22, // Octet String (len 34) - 包装私钥
|
||||
0x04, 0x20, // Octet String (len 32) - 内部 CurvePrivateKey
|
||||
];
|
||||
|
||||
pub fn generate_deterministic_cert(
|
||||
password: &str,
|
||||
) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
|
||||
// 基于密码生成 32 字节的确定性种子
|
||||
let seed = derive_seed_from_password(password);
|
||||
|
||||
let mut pkcs8_bytes = Vec::with_capacity(ED25519_PKCS8_V1_PREFIX.len() + seed.len());
|
||||
pkcs8_bytes.extend_from_slice(&ED25519_PKCS8_V1_PREFIX);
|
||||
pkcs8_bytes.extend_from_slice(&seed);
|
||||
let private_key_der = pki_types::PrivateKeyDer::try_from(pkcs8_bytes.clone())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to convert private key: {}", e))?;
|
||||
let key_pair = KeyPair::from_der_and_sign_algo(&private_key_der, &PKCS_ED25519)
|
||||
.context("Failed to load determinstic Ed25519 key")?;
|
||||
|
||||
let mut params = CertificateParams::new(vec!["deterministic-node".to_string()])?;
|
||||
params
|
||||
.distinguished_name
|
||||
.push(DnType::CommonName, "Deterministic Self-Signed Cert");
|
||||
|
||||
let not_before = OffsetDateTime::UNIX_EPOCH;
|
||||
let not_after = not_before + Duration::days(365 * 1000);
|
||||
params.not_before = not_before;
|
||||
params.not_after = not_after;
|
||||
|
||||
let serial_number_bytes = derive_serial_number(password);
|
||||
params.serial_number = Some(SerialNumber::from_slice(&serial_number_bytes));
|
||||
|
||||
// Ed25519 签名是确定性的 (RFC 8032),不需要随机数,因此每次运行结果字节完全一致
|
||||
let cert = params
|
||||
.self_signed(&key_pair)
|
||||
.context("Failed to sign certificate")?;
|
||||
|
||||
let cert_der = cert.der().clone();
|
||||
|
||||
let private_key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(pkcs8_bytes));
|
||||
|
||||
Ok((cert_der, private_key_der))
|
||||
}
|
||||
|
||||
fn derive_serial_number(password: &str) -> [u8; 20] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"vnt-serial-v1:");
|
||||
hasher.update(password.as_bytes());
|
||||
let result = hasher.finalize();
|
||||
|
||||
let mut serial = [0u8; 20];
|
||||
serial.copy_from_slice(&result[..20]);
|
||||
serial
|
||||
}
|
||||
|
||||
fn derive_seed_from_password(password: &str) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"vnt-ed25519-seed-v1:");
|
||||
hasher.update(password.as_bytes());
|
||||
|
||||
let mut result = hasher.finalize();
|
||||
for _ in 0..10 {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(result);
|
||||
result = hasher.finalize();
|
||||
}
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_deterministic_cert_generation() {
|
||||
let password = "test_password_123";
|
||||
|
||||
// 生成两次证书
|
||||
let (cert1, key1) = generate_deterministic_cert(password).unwrap();
|
||||
let (cert2, key2) = generate_deterministic_cert(password).unwrap();
|
||||
|
||||
// 验证私钥相同
|
||||
assert_eq!(
|
||||
key1.secret_der(),
|
||||
key2.secret_der(),
|
||||
"Private keys should be identical"
|
||||
);
|
||||
|
||||
// 验证证书相同
|
||||
assert_eq!(cert1, cert2, "Certificates should be identical");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_different_passwords_generate_different_certs() {
|
||||
let (cert1, key1) = generate_deterministic_cert("password1").unwrap();
|
||||
let (cert2, key2) = generate_deterministic_cert("password2").unwrap();
|
||||
|
||||
// 不同密码应该生成不同的证书和密钥
|
||||
assert_ne!(cert1, cert2);
|
||||
assert_ne!(key1.secret_der(), key2.secret_der());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod cert;
|
||||
pub mod verifier;
|
||||
@@ -0,0 +1,228 @@
|
||||
use anyhow::Context;
|
||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||
use rustls::{CertificateError, ClientConfig, Error, SignatureScheme};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FingerprintVerifier {
|
||||
pub expected_fingerprint: [u8; 32],
|
||||
}
|
||||
impl FingerprintVerifier {
|
||||
pub fn new(expected_fingerprint: [u8; 32]) -> Self {
|
||||
Self {
|
||||
expected_fingerprint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerCertVerifier for FingerprintVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, Error> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(end_entity.as_ref());
|
||||
let calculated_hash: [u8; 32] = hasher.finalize().into();
|
||||
|
||||
if calculated_hash == self.expected_fingerprint {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
} else {
|
||||
log::error!(
|
||||
"Certificate fingerprint mismatch. Expected: {:X?}, Got: {:X?}",
|
||||
self.expected_fingerprint,
|
||||
calculated_hash
|
||||
);
|
||||
Err(Error::InvalidCertificate(CertificateError::BadSignature))
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
vec![
|
||||
// RSA schemes
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA256,
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA384,
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA512,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA256,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA384,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA512,
|
||||
// ECDSA schemes
|
||||
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
|
||||
// EdDSA schemes
|
||||
rustls::SignatureScheme::ED25519,
|
||||
rustls::SignatureScheme::ED448,
|
||||
]
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct InsecureVerifier;
|
||||
|
||||
impl ServerCertVerifier for InsecureVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, Error> {
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![
|
||||
SignatureScheme::RSA_PKCS1_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512,
|
||||
SignatureScheme::RSA_PSS_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA512,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
SignatureScheme::ECDSA_NISTP521_SHA512,
|
||||
SignatureScheme::ED25519,
|
||||
SignatureScheme::ED448,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_root_cert() -> anyhow::Result<rustls::RootCertStore> {
|
||||
let mut root_cert_store = rustls::RootCertStore::empty();
|
||||
let certs = rustls_native_certs::load_native_certs().certs;
|
||||
for cert in certs {
|
||||
root_cert_store
|
||||
.add(cert)
|
||||
.context("Failed to add native cert to store")?;
|
||||
}
|
||||
Ok(root_cert_store)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum CertValidationMode {
|
||||
#[default]
|
||||
InsecureSkipVerification,
|
||||
VerifyFingerprint([u8; 32]),
|
||||
Standard,
|
||||
}
|
||||
impl FromStr for CertValidationMode {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let val = value.trim().to_lowercase();
|
||||
if val == "skip" {
|
||||
return Ok(CertValidationMode::InsecureSkipVerification);
|
||||
}
|
||||
if val == "standard" {
|
||||
return Ok(CertValidationMode::Standard);
|
||||
}
|
||||
if let Some(hex_str) = val.strip_prefix("finger:") {
|
||||
let decoded =
|
||||
hex::decode(hex_str).map_err(|e| format!("Invalid hex in fingerprint: {}", e))?;
|
||||
|
||||
if decoded.len() != 32 {
|
||||
return Err(format!(
|
||||
"Fingerprint must be 32 bytes (64 hex chars), got {} bytes",
|
||||
decoded.len()
|
||||
));
|
||||
}
|
||||
|
||||
let mut arr = [0u8; 32];
|
||||
arr.copy_from_slice(&decoded);
|
||||
return Ok(CertValidationMode::VerifyFingerprint(arr));
|
||||
}
|
||||
Err(format!("Unknown certificate validation mode: {}", value))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for CertValidationMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CertValidationMode::InsecureSkipVerification => {
|
||||
write!(f, "skip")
|
||||
}
|
||||
CertValidationMode::Standard => {
|
||||
write!(f, "standard")
|
||||
}
|
||||
CertValidationMode::VerifyFingerprint(fingerprint) => {
|
||||
let hex_str = hex::encode(fingerprint);
|
||||
|
||||
write!(f, "finger:{}", hex_str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CertValidationMode {
|
||||
pub fn build_verifier(&self) -> anyhow::Result<std::sync::Arc<dyn ServerCertVerifier>> {
|
||||
match self {
|
||||
CertValidationMode::InsecureSkipVerification => {
|
||||
Ok(std::sync::Arc::new(InsecureVerifier))
|
||||
}
|
||||
CertValidationMode::VerifyFingerprint(fingerprint) => {
|
||||
Ok(std::sync::Arc::new(FingerprintVerifier {
|
||||
expected_fingerprint: *fingerprint,
|
||||
}))
|
||||
}
|
||||
CertValidationMode::Standard => {
|
||||
let root_store = load_root_cert()?;
|
||||
let verifier =
|
||||
rustls::client::WebPkiServerVerifier::builder(std::sync::Arc::new(root_store))
|
||||
.build()?;
|
||||
|
||||
Ok(verifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn create_tls_client_config(&self) -> anyhow::Result<ClientConfig> {
|
||||
let verifier = self.build_verifier()?;
|
||||
|
||||
let config = ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(verifier)
|
||||
.with_no_client_auth();
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use crate::context::NetworkAddr;
|
||||
use crate::nat::internal_nat::InternalNatInbound;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tun::TunDataInbound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EnhancedTunInbound {
|
||||
Tun(TunDataInbound),
|
||||
Nat(InternalNatInbound),
|
||||
}
|
||||
impl EnhancedTunInbound {
|
||||
pub async fn inbound(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
|
||||
match self {
|
||||
EnhancedTunInbound::Tun(tun) => tun.send(data, net).await,
|
||||
EnhancedTunInbound::Nat(nat) => nat.send(&data, net).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
use crate::enhanced_tunnel::outbound::EnhancedOutbound;
|
||||
use crate::protocol::ip_packet_protocol::HEAD_LENGTH;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::utils::task_control::{SubTask, TaskGroup};
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::BytesMut;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::io;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tun_rs::async_framed::{Decoder, DeviceFramedRead, DeviceFramedWrite, Encoder};
|
||||
use tun_rs::{AsyncDevice, DeviceBuilder};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceIOManager {
|
||||
task_group: TaskGroup,
|
||||
device: DeviceMutex,
|
||||
}
|
||||
type DeviceMutex = Arc<tokio::sync::Mutex<(Option<DeviceTask>, Option<(Ipv4Addr, u8)>)>>;
|
||||
pub struct DeviceTask {
|
||||
device: Arc<AsyncDevice>,
|
||||
task_recv: SubTask,
|
||||
task_send: SubTask,
|
||||
}
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DeviceConfig {
|
||||
pub tun_name: Option<String>,
|
||||
#[cfg(unix)]
|
||||
pub tun_fd: Option<i32>,
|
||||
pub mtu: Option<u16>,
|
||||
}
|
||||
|
||||
impl DeviceConfig {
|
||||
pub fn set_tun_name(mut self, tun_name: String) -> Self {
|
||||
self.tun_name = Some(tun_name);
|
||||
self
|
||||
}
|
||||
#[cfg(unix)]
|
||||
pub fn set_tun_fd(mut self, tun_fd: i32) -> Self {
|
||||
self.tun_fd = Some(tun_fd);
|
||||
self
|
||||
}
|
||||
pub fn set_mtu(mut self, mtu: u16) -> Self {
|
||||
self.mtu = Some(mtu);
|
||||
self
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct TunInbound {
|
||||
pub(crate) sender: Sender<TransmissionBytes>,
|
||||
}
|
||||
|
||||
pub struct TunReceiver {
|
||||
receiver: Receiver<TransmissionBytes>,
|
||||
}
|
||||
pub fn tun_channel() -> (TunInbound, TunReceiver) {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel(1024);
|
||||
(TunInbound { sender }, TunReceiver { receiver })
|
||||
}
|
||||
|
||||
impl DeviceIOManager {
|
||||
pub fn new(task_group: TaskGroup) -> DeviceIOManager {
|
||||
Self {
|
||||
task_group,
|
||||
device: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
pub async fn stop_task(&self) {
|
||||
let mut guard = self.device.lock().await;
|
||||
if let Some(dev) = guard.0.take() {
|
||||
dev.task_recv.stop().await;
|
||||
dev.task_send.stop().await;
|
||||
}
|
||||
}
|
||||
pub async fn start_task(
|
||||
&self,
|
||||
device_config: DeviceConfig,
|
||||
receiver: TunReceiver,
|
||||
enhanced_outbound: EnhancedOutbound,
|
||||
) -> anyhow::Result<()> {
|
||||
self.stop_task().await;
|
||||
let task = create(
|
||||
&self.task_group,
|
||||
device_config,
|
||||
receiver.receiver,
|
||||
enhanced_outbound,
|
||||
)?;
|
||||
self.device.lock().await.0.replace(task);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub async fn tun_if_index(&self) -> anyhow::Result<u32> {
|
||||
let guard = self.device.lock().await;
|
||||
if let Some(v) = &guard.0 {
|
||||
Ok(v.device.if_index()?)
|
||||
} else {
|
||||
bail!("device doesn't exist")
|
||||
}
|
||||
}
|
||||
pub async fn set_network(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
||||
let mut guard = self.device.lock().await;
|
||||
let Some(dev) = guard.0.as_ref() else {
|
||||
bail!("未启动tun")
|
||||
};
|
||||
if let Some(v) = guard.1.as_ref()
|
||||
&& v.0 == ip
|
||||
&& v.1 == prefix_len
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
dev.device
|
||||
.set_network_address(ip, prefix_len, None)
|
||||
.context("设置IP失败")?;
|
||||
guard.1 = Some((ip, prefix_len));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
||||
#[cfg(unix)]
|
||||
if let Some(fd) = config.tun_fd {
|
||||
// SAFETY: Caller must ensure fd is a valid, open file descriptor for a TUN device.
|
||||
// Using an invalid fd may cause undefined behavior.
|
||||
unsafe { return Ok(AsyncDevice::from_fd(fd)?) }
|
||||
}
|
||||
let mut builder = DeviceBuilder::new();
|
||||
if let Some(tun_name) = config.tun_name {
|
||||
builder = builder.name(tun_name);
|
||||
}
|
||||
if let Some(mtu) = config.mtu {
|
||||
builder = builder.mtu(mtu);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
builder = builder.metric(1);
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
builder = builder.offload(true);
|
||||
}
|
||||
let dev = builder.build_async().context("创建tun失败")?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
_ = dev.set_tx_queue_len(1000);
|
||||
}
|
||||
Ok(dev)
|
||||
}
|
||||
fn create(
|
||||
task_group: &TaskGroup,
|
||||
config: DeviceConfig,
|
||||
receiver: Receiver<TransmissionBytes>,
|
||||
enhanced_outbound: EnhancedOutbound,
|
||||
) -> anyhow::Result<DeviceTask> {
|
||||
let device = Arc::new(create_tun(config)?);
|
||||
|
||||
let device_framed_read = DeviceFramedRead::new(device.clone(), BytesCodec::new());
|
||||
let device_framed_write = DeviceFramedWrite::new(device.clone(), BytesCodec::new());
|
||||
|
||||
let task_recv = task_group.spawn(async move {
|
||||
if let Err(e) = in_tun_loop(receiver, device_framed_write).await {
|
||||
log::error!("in_tun_loop error: {e:?}")
|
||||
}
|
||||
});
|
||||
let task_send = task_group.spawn(async move {
|
||||
if let Err(e) = out_tun_loop(device_framed_read, enhanced_outbound).await {
|
||||
log::error!("out_tun_loop error: {e:?}");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(DeviceTask {
|
||||
device,
|
||||
task_recv,
|
||||
task_send,
|
||||
})
|
||||
}
|
||||
|
||||
async fn in_tun_loop(
|
||||
mut receiver: Receiver<TransmissionBytes>,
|
||||
mut device_framed_write: DeviceFramedWrite<BytesCodec, Arc<AsyncDevice>>,
|
||||
) -> anyhow::Result<()> {
|
||||
while let Some(data) = receiver.recv().await {
|
||||
match device_framed_write.send(data).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
log::error!("send to tun error: {:?}", e);
|
||||
return Err(anyhow::anyhow!(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn out_tun_loop(
|
||||
mut device_framed_read: DeviceFramedRead<BytesCodec, Arc<AsyncDevice>>,
|
||||
enhanced_outbound: EnhancedOutbound,
|
||||
) -> anyhow::Result<()> {
|
||||
while let Some(rs) = device_framed_read.next().await {
|
||||
let bytes_mut = rs?;
|
||||
enhanced_outbound.ipv4_outbound(bytes_mut).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
pub struct BytesCodec(());
|
||||
impl BytesCodec {
|
||||
pub fn new() -> BytesCodec {
|
||||
BytesCodec(())
|
||||
}
|
||||
}
|
||||
impl Decoder for BytesCodec {
|
||||
type Item = TransmissionBytes;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<TransmissionBytes>, io::Error> {
|
||||
if !buf.is_empty() {
|
||||
let mut bytes = TransmissionBytes::new_offset(HEAD_LENGTH);
|
||||
bytes.put(buf)?;
|
||||
buf.clear();
|
||||
Ok(Some(bytes))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<TransmissionBytes> for BytesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: TransmissionBytes, buf: &mut BytesMut) -> Result<(), io::Error> {
|
||||
buf.reserve(data.len());
|
||||
buf.extend_from_slice(&data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
mod general;
|
||||
pub use general::*;
|
||||
mod sender;
|
||||
pub use sender::*;
|
||||
|
||||
pub mod enhanced_tun;
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::context::NetworkAddr;
|
||||
use crate::nat::AllowSubnetExternalRoute;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tun::TunInbound;
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TunDataInbound {
|
||||
allow_subnet: AllowSubnetExternalRoute,
|
||||
tun_inbound: TunInbound,
|
||||
}
|
||||
impl TunDataInbound {
|
||||
pub fn new(tun_inbound: TunInbound, allow_subnet: AllowSubnetExternalRoute) -> Self {
|
||||
Self {
|
||||
allow_subnet,
|
||||
tun_inbound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TunDataInbound {
|
||||
pub async fn send(&self, data: TransmissionBytes, net: &NetworkAddr) -> anyhow::Result<()> {
|
||||
if data[0] >> 4 != 4 {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
|
||||
return Ok(());
|
||||
};
|
||||
let dest = ipv4.get_destination();
|
||||
if net.network().contains(&dest)
|
||||
|| dest == net.broadcast
|
||||
|| dest.is_broadcast()
|
||||
|| dest.is_multicast()
|
||||
|| self.allow_subnet.allow(&dest)
|
||||
{
|
||||
self.tun_inbound.sender.send(data).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub(crate) mod p2p;
|
||||
pub mod server;
|
||||
|
||||
pub(crate) mod outbound;
|
||||
@@ -0,0 +1,286 @@
|
||||
use crate::compression::PacketCompression;
|
||||
use crate::context::{NetworkAddr, ServerInfoCollection, SharedNetworkAddr, TrafficStats};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::fec::FecEncoder;
|
||||
use crate::nat::SubnetExternalRoute;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::outbound::P2pOutbound;
|
||||
use crate::tunnel_core::server::outbound::ServerOutbound;
|
||||
use anyhow::bail;
|
||||
use bytes::Bytes;
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BasicOutbound {
|
||||
server_outbound: ServerOutbound,
|
||||
p2p_outbound: Option<P2pOutbound>,
|
||||
packet_crypto: PacketCrypto,
|
||||
}
|
||||
|
||||
impl BasicOutbound {
|
||||
pub fn new(
|
||||
server_outbound: ServerOutbound,
|
||||
p2p_outbound: Option<P2pOutbound>,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> Self {
|
||||
Self {
|
||||
server_outbound,
|
||||
p2p_outbound,
|
||||
packet_crypto,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取加密保留空间大小
|
||||
pub fn encrypt_reserve(&self) -> usize {
|
||||
self.packet_crypto.encrypt_reserve()
|
||||
}
|
||||
|
||||
/// 加密数据包
|
||||
pub fn encrypt_in_place(
|
||||
&self,
|
||||
packet: &mut NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(self.packet_crypto.encrypt_in_place(packet)?)
|
||||
}
|
||||
|
||||
/// 发送原始数据包到指定目标(通过P2P或服务器)
|
||||
pub async fn send_raw(
|
||||
&self,
|
||||
dest: Ipv4Addr,
|
||||
packet: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
let packet = packet.into_bytes();
|
||||
if let Some(p2p) = self.p2p_outbound.as_ref()
|
||||
&& let Some(route) = p2p.get_route_by_id(&dest)
|
||||
{
|
||||
p2p.send_raw_to(packet, &route.route_key()).await?;
|
||||
} else {
|
||||
self.server_outbound.send_raw(dest, packet).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 发送到默认服务器
|
||||
pub async fn send_default_raw(
|
||||
&self,
|
||||
packet: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
let bytes = packet.into_buffer().into_bytes().freeze();
|
||||
self.server_outbound
|
||||
.send_default_raw(NetPacket::new(bytes)?)
|
||||
.await
|
||||
}
|
||||
|
||||
/// 广播发送
|
||||
pub async fn send_raw_broadcast(
|
||||
&self,
|
||||
exclude_ips: Option<Vec<Ipv4Addr>>,
|
||||
packet: NetPacket<Bytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.server_outbound
|
||||
.send_raw_broadcast(exclude_ips, packet)
|
||||
.await
|
||||
}
|
||||
|
||||
/// 检查是否存在到目标的路由
|
||||
pub fn exists_route(&self, dest: &Ipv4Addr) -> bool {
|
||||
if let Some(p2p) = self.p2p_outbound.as_ref()
|
||||
&& p2p.exists_route_by_id(dest)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.server_outbound.exists_route(dest)
|
||||
}
|
||||
|
||||
/// P2P广播(内部转换类型)
|
||||
pub fn p2p_broadcast_transmission(
|
||||
&self,
|
||||
list: &[Ipv4Addr],
|
||||
max_count: usize,
|
||||
packet: &NetPacket<Bytes>,
|
||||
) -> Option<Vec<Ipv4Addr>> {
|
||||
if let Some(p2p) = self.p2p_outbound.as_ref() {
|
||||
let vec = p2p.p2p_broadcast(list, max_count, packet);
|
||||
if vec.is_empty() { None } else { Some(vec) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送加密后的数据包
|
||||
pub async fn send_encrypted_packet(
|
||||
&self,
|
||||
dest: Ipv4Addr,
|
||||
mut packet: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
// 加密
|
||||
self.packet_crypto.encrypt_in_place(&mut packet)?;
|
||||
|
||||
// 发送
|
||||
if let Some(p2p) = self.p2p_outbound.as_ref()
|
||||
&& let Some(route) = p2p.get_route_by_id(&dest)
|
||||
{
|
||||
let bytes = packet.into_buffer().into_bytes().freeze();
|
||||
p2p.send_raw_to(NetPacket::new(bytes)?, &route.route_key())
|
||||
.await?;
|
||||
} else {
|
||||
let bytes = packet.into_buffer().into_bytes().freeze();
|
||||
self.server_outbound
|
||||
.send_raw(dest, NetPacket::new(bytes)?)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct HybridOutbound {
|
||||
network: SharedNetworkAddr,
|
||||
server_info: ServerInfoCollection,
|
||||
traffic_stats: TrafficStats,
|
||||
basic_outbound: BasicOutbound,
|
||||
packet_compression: PacketCompression,
|
||||
external_route: SubnetExternalRoute,
|
||||
fec_encoder: Option<FecEncoder>,
|
||||
}
|
||||
impl HybridOutbound {
|
||||
pub fn new(
|
||||
network: SharedNetworkAddr,
|
||||
server_info: ServerInfoCollection,
|
||||
traffic_stats: TrafficStats,
|
||||
basic_outbound: BasicOutbound,
|
||||
packet_compression: PacketCompression,
|
||||
external_route: SubnetExternalRoute,
|
||||
fec_encoder: Option<FecEncoder>,
|
||||
) -> Self {
|
||||
Self {
|
||||
network,
|
||||
server_info,
|
||||
traffic_stats,
|
||||
basic_outbound,
|
||||
packet_compression,
|
||||
external_route,
|
||||
fec_encoder,
|
||||
}
|
||||
}
|
||||
pub async fn outbound_raw(
|
||||
&self,
|
||||
dest: Ipv4Addr,
|
||||
mut packet: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
if packet.src_id() == 0 {
|
||||
if let Some(ip) = self.network.ip() {
|
||||
packet.set_src_id(ip.into());
|
||||
} else {
|
||||
bail!("Not src ip")
|
||||
}
|
||||
}
|
||||
|
||||
let len = packet.buffer().len() as u64;
|
||||
|
||||
if let Some(fec_encoder) = &self.fec_encoder {
|
||||
packet = fec_encoder.encode(packet)?;
|
||||
}
|
||||
|
||||
self.basic_outbound.send_raw(dest, packet).await?;
|
||||
self.traffic_stats.record_tx(dest, len);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn ipv4_outbound_common(&self, data: TransmissionBytes) -> anyhow::Result<()> {
|
||||
let Some(net) = self.network.get() else {
|
||||
bail!("Not src ip")
|
||||
};
|
||||
self.ipv4_outbound(net, data).await
|
||||
}
|
||||
pub async fn ipv4_outbound(
|
||||
&self,
|
||||
net: NetworkAddr,
|
||||
mut data: TransmissionBytes,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(ipv4) = Ipv4Packet::new(data.as_ref()) else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut dest = ipv4.get_destination();
|
||||
let len = data.len() as u64;
|
||||
data.retreat_head(HEAD_LENGTH)?;
|
||||
let mut packet = NetPacket::new(data)?;
|
||||
packet.set_msg_type(MsgType::Turn);
|
||||
packet.set_src_id(net.ip.into());
|
||||
packet.set_ttl(5);
|
||||
// 路由
|
||||
if !net.network().contains(&dest) {
|
||||
if let Some(v) = self.external_route.route(&dest) {
|
||||
dest = v;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
packet.set_dest_id(dest.into());
|
||||
|
||||
packet = self
|
||||
.packet_compression
|
||||
.compress(packet, self.basic_outbound.encrypt_reserve())?;
|
||||
|
||||
if let Some(fec_encoder) = &self.fec_encoder {
|
||||
packet = fec_encoder.encode(packet)?;
|
||||
}
|
||||
|
||||
// 发送
|
||||
self.basic_outbound
|
||||
.send_encrypted_packet(dest, packet)
|
||||
.await?;
|
||||
self.traffic_stats.record_tx(dest, len);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn ipv4_gateway_outbound(
|
||||
&self,
|
||||
net: NetworkAddr,
|
||||
mut data: TransmissionBytes,
|
||||
) -> anyhow::Result<()> {
|
||||
data.retreat_head(HEAD_LENGTH)?;
|
||||
let mut packet = NetPacket::new(data)?;
|
||||
packet.set_msg_type(MsgType::Turn);
|
||||
packet.set_src_id(net.ip.into());
|
||||
packet.set_dest_id(net.gateway.into());
|
||||
packet.set_ttl(5);
|
||||
packet.set_gateway_flag(true);
|
||||
self.basic_outbound.send_default_raw(packet).await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn ipv4_broadcast_outbound(
|
||||
&self,
|
||||
net: NetworkAddr,
|
||||
mut data: TransmissionBytes,
|
||||
) -> anyhow::Result<()> {
|
||||
data.retreat_head(HEAD_LENGTH)?;
|
||||
let mut packet = NetPacket::new(data)?;
|
||||
packet.set_msg_type(MsgType::Broadcast);
|
||||
packet.set_src_id(net.ip.into());
|
||||
packet.set_dest_id(Ipv4Addr::BROADCAST.into());
|
||||
packet.set_ttl(5);
|
||||
let mut packet = self
|
||||
.packet_compression
|
||||
.compress(packet, self.basic_outbound.encrypt_reserve())?;
|
||||
self.basic_outbound.encrypt_in_place(&mut packet)?;
|
||||
let packet_bytes = packet.into_bytes();
|
||||
let list = self.server_info.client_online_ips();
|
||||
let exclude_ips = self
|
||||
.basic_outbound
|
||||
.p2p_broadcast_transmission(&list, 16, &packet_bytes);
|
||||
if let Some(exclude_ips) = &exclude_ips
|
||||
&& exclude_ips.len() == list.len()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.basic_outbound
|
||||
.send_raw_broadcast(exclude_ips, packet_bytes)
|
||||
.await
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn has_route(&self, dest: &Ipv4Addr) -> bool {
|
||||
self.basic_outbound.exists_route(dest)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
use crate::compression::PacketCompression;
|
||||
use crate::context::{NetworkRoute, PacketLossStats};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||
use crate::fec::FecDecoder;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::outbound::P2pOutbound;
|
||||
use crate::tunnel_core::p2p::route_table::{Route, RouteTable};
|
||||
use anyhow::bail;
|
||||
use rust_p2p_core::route::RouteKey;
|
||||
use rust_p2p_core::tunnel::Tunnel;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
struct PacketContext {
|
||||
msg_type: MsgType,
|
||||
src_ip: Ipv4Addr,
|
||||
dest_ip: Ipv4Addr,
|
||||
max_ttl: u8,
|
||||
ttl: u8,
|
||||
}
|
||||
|
||||
pub(crate) struct P2pInboundConfig {
|
||||
pub network_route: NetworkRoute,
|
||||
pub route_table: RouteTable,
|
||||
pub packet_loss_stats: PacketLossStats,
|
||||
pub packet_crypto: PacketCrypto,
|
||||
pub packet_compression: PacketCompression,
|
||||
pub enhanced_inbound: EnhancedInbound,
|
||||
pub fec_decoder: FecDecoder,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct P2pInboundHandler {
|
||||
network_route: NetworkRoute,
|
||||
route_table: RouteTable,
|
||||
packet_loss_stats: PacketLossStats,
|
||||
packet_crypto: PacketCrypto,
|
||||
packet_compression: PacketCompression,
|
||||
enhanced_inbound: EnhancedInbound,
|
||||
fec_decoder: FecDecoder,
|
||||
}
|
||||
|
||||
impl P2pInboundHandler {
|
||||
pub fn new(config: P2pInboundConfig) -> Self {
|
||||
Self {
|
||||
network_route: config.network_route,
|
||||
route_table: config.route_table,
|
||||
packet_loss_stats: config.packet_loss_stats,
|
||||
packet_crypto: config.packet_crypto,
|
||||
packet_compression: config.packet_compression,
|
||||
enhanced_inbound: config.enhanced_inbound,
|
||||
fec_decoder: config.fec_decoder,
|
||||
}
|
||||
}
|
||||
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
|
||||
self.network_route.network_contains(ip)
|
||||
}
|
||||
pub async fn next_handle(
|
||||
&self,
|
||||
buf: TransmissionBytes,
|
||||
route_key: RouteKey,
|
||||
p2p_socket_manager: &P2pOutbound,
|
||||
tunnel: &mut Tunnel,
|
||||
) {
|
||||
if let Err(e) = self
|
||||
.next_handle_impl(buf, route_key, p2p_socket_manager, tunnel)
|
||||
.await
|
||||
{
|
||||
log::warn!(
|
||||
"Error while handling P2pInboundHandler: {:?},route={route_key:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
async fn next_handle_impl(
|
||||
&self,
|
||||
buf: TransmissionBytes,
|
||||
route_key: RouteKey,
|
||||
p2p_socket_manager: &P2pOutbound,
|
||||
tunnel: &mut Tunnel,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut net_packet = NetPacket::new(buf)?;
|
||||
let msg_type = net_packet.msg_type()?;
|
||||
let src_ip = Ipv4Addr::from(net_packet.src_id());
|
||||
let dest_ip = Ipv4Addr::from(net_packet.dest_id());
|
||||
if src_ip == dest_ip {
|
||||
return Ok(());
|
||||
}
|
||||
net_packet.decr_ttl();
|
||||
|
||||
let max_ttl = net_packet.max_ttl();
|
||||
let ttl = net_packet.ttl();
|
||||
if max_ttl <= ttl {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(net) = self.network_route.network.get() else {
|
||||
bail!("未找到自身IP")
|
||||
};
|
||||
if net.ip != dest_ip
|
||||
&& !dest_ip.is_broadcast()
|
||||
&& !dest_ip.is_unspecified()
|
||||
&& dest_ip != net.broadcast
|
||||
{
|
||||
// 帮忙转发数据包
|
||||
if ttl >= 1 {
|
||||
if let Some(route) = p2p_socket_manager.get_route_by_id(&dest_ip) {
|
||||
p2p_socket_manager
|
||||
.send_raw_to(net_packet.into_bytes(), &route.route_key())
|
||||
.await?;
|
||||
} else {
|
||||
log::debug!("未找到到 {} 的路由,无法转发", dest_ip);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if msg_type == MsgType::Quic {
|
||||
if net_packet.is_fec() {
|
||||
let packets = self.fec_decoder.receive(net_packet)?;
|
||||
if let Some(packets) = packets {
|
||||
for pkt in packets {
|
||||
self.enhanced_inbound
|
||||
.inbound(&net, msg_type, src_ip, pkt)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
self.enhanced_inbound
|
||||
.inbound(&net, msg_type, src_ip, net_packet)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 解密
|
||||
self.packet_crypto.decrypt_in_place(&mut net_packet)?;
|
||||
|
||||
let ctx = PacketContext {
|
||||
msg_type,
|
||||
src_ip,
|
||||
dest_ip,
|
||||
max_ttl,
|
||||
ttl,
|
||||
};
|
||||
|
||||
// FEC 解码(始终尝试解码,如果有 FEC 标志)
|
||||
if net_packet.is_fec() {
|
||||
let packets = self.fec_decoder.receive(net_packet)?;
|
||||
if let Some(packets) = packets {
|
||||
for pkt in packets {
|
||||
let pkt = self.packet_compression.decompress(pkt)?;
|
||||
self.process_decompressed_packet(&net, route_key, tunnel, pkt, &ctx)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 解压缩
|
||||
let net_packet = self.packet_compression.decompress(net_packet)?;
|
||||
self.process_decompressed_packet(&net, route_key, tunnel, net_packet, &ctx)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn process_decompressed_packet(
|
||||
&self,
|
||||
net: &crate::context::NetworkAddr,
|
||||
route_key: RouteKey,
|
||||
tunnel: &mut Tunnel,
|
||||
net_packet: NetPacket<TransmissionBytes>,
|
||||
ctx: &PacketContext,
|
||||
) -> anyhow::Result<()> {
|
||||
match ctx.msg_type {
|
||||
MsgType::Turn | MsgType::Broadcast | MsgType::ExcludeBroadcast => {
|
||||
self.enhanced_inbound
|
||||
.inbound(net, ctx.msg_type, ctx.src_ip, net_packet)
|
||||
.await?;
|
||||
}
|
||||
MsgType::Ping => {
|
||||
let metric = ctx.max_ttl - ctx.ttl;
|
||||
self.route_table
|
||||
.add_route_if_absent(ctx.src_ip, Route::from_default_rt(route_key, metric));
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + 8,
|
||||
self.packet_crypto.encrypt_reserve(),
|
||||
))?;
|
||||
packet.set_msg_type(MsgType::Pong);
|
||||
packet.set_ttl(1);
|
||||
packet.set_src_id(ctx.dest_ip.into());
|
||||
packet.set_dest_id(ctx.src_ip.into());
|
||||
packet.set_payload(net_packet.payload())?;
|
||||
self.packet_crypto.encrypt_in_place(&mut packet)?;
|
||||
tunnel
|
||||
.send_to(packet.into_bytes().into_buffer(), route_key.addr())
|
||||
.await?;
|
||||
}
|
||||
MsgType::Pong => {
|
||||
if net_packet.payload().len() >= 8 {
|
||||
let metric = ctx.max_ttl - ctx.ttl;
|
||||
let time = i64::from_be_bytes(net_packet.payload()[..8].try_into()?);
|
||||
let now = crate::utils::time::now_ts_ms();
|
||||
if now >= time {
|
||||
self.route_table.add_route(
|
||||
ctx.src_ip,
|
||||
Route::from(route_key, metric, (now - time) as _),
|
||||
);
|
||||
self.packet_loss_stats.record_received(ctx.src_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
MsgType::PunchStart1 => {}
|
||||
MsgType::PunchStart2 => {}
|
||||
MsgType::PunchReq => {
|
||||
if let IpAddr::V4(ip) = route_key.addr().ip()
|
||||
&& self.network_contains(&ip)
|
||||
{
|
||||
log::info!("===========loop PunchReq {route_key:?} {:?}", ctx.src_ip);
|
||||
return Ok(());
|
||||
}
|
||||
log::info!(
|
||||
"PunchReq 打洞成功 {}->{},route={route_key:?}",
|
||||
ctx.src_ip,
|
||||
ctx.dest_ip
|
||||
);
|
||||
self.route_table.add_owner_route(ctx.src_ip, route_key);
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + 8,
|
||||
self.packet_crypto.encrypt_reserve(),
|
||||
))?;
|
||||
packet.set_msg_type(MsgType::PunchRes);
|
||||
packet.set_ttl(1);
|
||||
packet.set_src_id(ctx.dest_ip.into());
|
||||
packet.set_dest_id(ctx.src_ip.into());
|
||||
packet.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())?;
|
||||
|
||||
self.packet_crypto.encrypt_in_place(&mut packet)?;
|
||||
tunnel
|
||||
.send_to(packet.into_bytes().into_buffer(), route_key.addr())
|
||||
.await?;
|
||||
}
|
||||
MsgType::PunchRes => {
|
||||
if let IpAddr::V4(ip) = route_key.addr().ip()
|
||||
&& self.network_contains(&ip)
|
||||
{
|
||||
log::info!("===========loop PunchRes {route_key:?} {:?}", ctx.src_ip);
|
||||
return Ok(());
|
||||
}
|
||||
log::info!(
|
||||
"PunchRes 打洞成功 {}->{},route={route_key:?}",
|
||||
ctx.src_ip,
|
||||
ctx.dest_ip
|
||||
);
|
||||
self.route_table.add_owner_route(ctx.src_ip, route_key);
|
||||
}
|
||||
MsgType::PingTurn => {}
|
||||
MsgType::PongTurn => {}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn tcp_disconnect(&self, route_key: RouteKey) {
|
||||
if let Some(ip) = self.route_table.get_id_by_route_key(&route_key) {
|
||||
self.route_table.remove_route(&ip, &route_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub(crate) mod inbound;
|
||||
pub(crate) mod outbound;
|
||||
pub(crate) mod transport;
|
||||
|
||||
pub(crate) mod route_table;
|
||||
@@ -0,0 +1,133 @@
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::protocol::ip_packet_protocol::NetPacket;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::route_table::{Route, RouteTable};
|
||||
use bytes::Bytes;
|
||||
use rust_p2p_core::route::RouteKey;
|
||||
use rust_p2p_core::tunnel::SocketManager;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct P2pOutbound {
|
||||
manager: SocketManager,
|
||||
route_table: RouteTable,
|
||||
packet_crypto: PacketCrypto,
|
||||
}
|
||||
impl P2pOutbound {
|
||||
pub fn new(
|
||||
manager: SocketManager,
|
||||
route_table: RouteTable,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> Self {
|
||||
Self {
|
||||
manager,
|
||||
route_table,
|
||||
packet_crypto,
|
||||
}
|
||||
}
|
||||
pub fn encrypt_reserve(&self) -> usize {
|
||||
self.packet_crypto.encrypt_reserve()
|
||||
}
|
||||
// pub async fn send_raw(&self, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
|
||||
// let dest_id = Ipv4Addr::from(buf.dest_id());
|
||||
// let route = self.route_table.get_route_by_id(&dest_id)?;
|
||||
// self.manager
|
||||
// .send_to(buf.into_buffer(), &route.route_key())
|
||||
// .await?;
|
||||
// Ok(())
|
||||
// }
|
||||
// pub async fn send(&self, mut buf: NetPacket<TransmissionBytes>) -> anyhow::Result<()> {
|
||||
// let dest_id = Ipv4Addr::from(buf.dest_id());
|
||||
// let route = self.route_table.get_route_by_id(&dest_id)?;
|
||||
// self.packet_crypto.encrypt_in_place(&mut buf)?;
|
||||
// self.manager
|
||||
// .send_to(buf.into_buffer().into_bytes().freeze(), &route.route_key())
|
||||
// .await?;
|
||||
// Ok(())
|
||||
// }
|
||||
pub async fn send_raw_to(
|
||||
&self,
|
||||
buf: NetPacket<Bytes>,
|
||||
route_key: &RouteKey,
|
||||
) -> anyhow::Result<()> {
|
||||
self.manager.send_to(buf.into_buffer(), route_key).await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send_to(
|
||||
&self,
|
||||
mut buf: NetPacket<TransmissionBytes>,
|
||||
route_key: &RouteKey,
|
||||
) -> anyhow::Result<()> {
|
||||
self.packet_crypto.encrypt_in_place(&mut buf)?;
|
||||
self.manager
|
||||
.send_to(buf.into_buffer().into_bytes().freeze(), route_key)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_route_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
|
||||
self.route_table.get_route_by_id(id).ok()
|
||||
}
|
||||
pub fn get_p2p_route_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
|
||||
self.route_table.get_route_by_id(id).ok().filter(|v| v.is_direct())
|
||||
}
|
||||
pub fn exists_route_by_id(&self, id: &Ipv4Addr) -> bool {
|
||||
self.route_table.exists(id)
|
||||
}
|
||||
|
||||
// pub async fn send_to_id(
|
||||
// &self,
|
||||
// buf: NetPacket<TransmissionBytes>,
|
||||
// id: &Ipv4Addr,
|
||||
// ) -> anyhow::Result<bool> {
|
||||
// let Ok(route) = self.route_table.get_route_by_id(id) else {
|
||||
// return Ok(false);
|
||||
// };
|
||||
// self.send_to(buf, &route.route_key()).await?;
|
||||
// Ok(true)
|
||||
// }
|
||||
// pub fn try_send_to_id(
|
||||
// &self,
|
||||
// buf: NetPacket<TransmissionBytes>,
|
||||
// id: &Ipv4Addr,
|
||||
// ) -> anyhow::Result<bool> {
|
||||
// let Ok(route) = self.route_table.get_route_by_id(id) else {
|
||||
// return Ok(false);
|
||||
// };
|
||||
// self.try_send_to(buf, &route.route_key())?;
|
||||
// Ok(true)
|
||||
// }
|
||||
// pub fn try_send_to(
|
||||
// &self,
|
||||
// buf: NetPacket<TransmissionBytes>,
|
||||
// route_key: &RouteKey,
|
||||
// ) -> anyhow::Result<()> {
|
||||
// self.manager
|
||||
// .try_send_to(buf.into_buffer().into_bytes(), route_key)?;
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn p2p_broadcast(
|
||||
&self,
|
||||
ips: &[Ipv4Addr],
|
||||
max: usize,
|
||||
buf: &NetPacket<Bytes>,
|
||||
) -> Vec<Ipv4Addr> {
|
||||
let mut list = Vec::with_capacity(ips.len().min(max));
|
||||
|
||||
for id in ips {
|
||||
let Some(route) = self.get_p2p_route_by_id(id) else {
|
||||
continue;
|
||||
};
|
||||
if self
|
||||
.manager
|
||||
.try_send_to(buf.source_buf().clone(), &route.route_key())
|
||||
.is_ok()
|
||||
{
|
||||
list.push(*id);
|
||||
if list.len() >= max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use rust_p2p_core::route::{RouteKey, DEFAULT_RTT};
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Route {
|
||||
route_key: RouteKey,
|
||||
metric: u8,
|
||||
rtt: u32,
|
||||
}
|
||||
impl Route {
|
||||
pub fn from(route_key: RouteKey, metric: u8, rtt: u32) -> Self {
|
||||
Self {
|
||||
route_key,
|
||||
metric,
|
||||
rtt,
|
||||
}
|
||||
}
|
||||
pub fn from_default_rt(route_key: RouteKey, metric: u8) -> Self {
|
||||
Self {
|
||||
route_key,
|
||||
metric,
|
||||
rtt: DEFAULT_RTT,
|
||||
}
|
||||
}
|
||||
pub fn route_key(&self) -> RouteKey {
|
||||
self.route_key
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
self.metric == 1
|
||||
}
|
||||
pub fn rtt(&self) -> u32 {
|
||||
self.rtt
|
||||
}
|
||||
pub fn metric(&self) -> u8 {
|
||||
self.metric
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RouteTable {
|
||||
inner: Arc<RouteTableInner>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RouteTableInner {
|
||||
route_table: RwLock<HashMap<Ipv4Addr, Vec<Route>>>,
|
||||
route_key_time: Mutex<HashMap<(Ipv4Addr, RouteKey), Instant>>,
|
||||
route_key_owner: Mutex<HashMap<RouteKey, Ipv4Addr>>,
|
||||
}
|
||||
|
||||
impl Default for RouteTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RouteTableInner::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定 ID 的最优路由
|
||||
pub fn get_route_by_id(&self, id: &Ipv4Addr) -> anyhow::Result<Route> {
|
||||
self.inner
|
||||
.get_by_id(id)
|
||||
.ok_or_else(|| anyhow::anyhow!("route not found for {}", id))
|
||||
}
|
||||
|
||||
/// 检查是否存在到指定 ID 的路由
|
||||
pub fn exists(&self, id: &Ipv4Addr) -> bool {
|
||||
self.inner.get_by_id(id).is_some()
|
||||
}
|
||||
|
||||
/// 判断是否需要打洞(没有路由或只有中继路由)
|
||||
pub fn need_punch(&self, id: &Ipv4Addr) -> bool {
|
||||
let guard = self.inner.route_table.read();
|
||||
let Some(list) = guard.get(id) else {
|
||||
return true;
|
||||
};
|
||||
// 如果没有直连路由(metric=1),则需要打洞
|
||||
!list.iter().any(|r| r.is_direct())
|
||||
}
|
||||
|
||||
/// 获取直连路由数量(用于判断是否直连)
|
||||
pub fn p2p_num(&self, id: &Ipv4Addr) -> usize {
|
||||
let guard = self.inner.route_table.read();
|
||||
let Some(list) = guard.get(id) else {
|
||||
return 0;
|
||||
};
|
||||
list.iter().filter(|r| r.is_direct()).count()
|
||||
}
|
||||
|
||||
/// 添加 owner 路由(打洞请求响应时调用)
|
||||
pub fn add_owner_route(&self, id: Ipv4Addr, key: RouteKey) {
|
||||
self.inner.add_owner_route(id, key);
|
||||
}
|
||||
|
||||
/// 添加路由(心跳时调用,用于更新路由时间和添加跨节点转发路由)
|
||||
pub fn add_route(&self, id: Ipv4Addr, route: Route) {
|
||||
self.inner.add_route(id, route);
|
||||
}
|
||||
|
||||
/// 如果路由不存在则添加(用于 Ping 消息)
|
||||
pub fn add_route_if_absent(&self, id: Ipv4Addr, route: Route) {
|
||||
let guard = self.inner.route_table.read();
|
||||
if guard.contains_key(&id) {
|
||||
return;
|
||||
}
|
||||
drop(guard);
|
||||
self.inner.add_route(id, route);
|
||||
}
|
||||
|
||||
/// 获取所有路由表
|
||||
pub fn route_table(&self) -> Vec<(Ipv4Addr, Vec<Route>)> {
|
||||
let guard = self.inner.route_table.read();
|
||||
guard.iter().map(|(k, v)| (*k, v.clone())).collect()
|
||||
}
|
||||
|
||||
/// 根据 RouteKey 查找对应的 IP
|
||||
pub fn get_id_by_route_key(&self, route_key: &RouteKey) -> Option<Ipv4Addr> {
|
||||
let owner_map = self.inner.route_key_owner.lock();
|
||||
owner_map.get(route_key).copied()
|
||||
}
|
||||
|
||||
/// 移除指定 IP 和 RouteKey 的路由
|
||||
pub fn remove_route(&self, id: &Ipv4Addr, route_key: &RouteKey) {
|
||||
let mut table = self.inner.route_table.write();
|
||||
let mut owner_map = self.inner.route_key_owner.lock();
|
||||
let mut time_map = self.inner.route_key_time.lock();
|
||||
|
||||
if let Some(list) = table.get_mut(id) {
|
||||
list.retain(|r| r.route_key() != *route_key);
|
||||
if list.is_empty() {
|
||||
table.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(owner_id) = owner_map.get(route_key) {
|
||||
if owner_id == id {
|
||||
owner_map.remove(route_key);
|
||||
}
|
||||
}
|
||||
|
||||
time_map.remove(&(*id, *route_key));
|
||||
}
|
||||
|
||||
/// 移除过期的路由
|
||||
pub fn remove_oldest_route(&self, expired_time: Instant) {
|
||||
self.inner.remove_oldest_route(expired_time);
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteTableInner {
|
||||
fn get_by_id(&self, id: &Ipv4Addr) -> Option<Route> {
|
||||
let guard = self.route_table.read();
|
||||
let list = guard.get(id)?;
|
||||
list.first().cloned()
|
||||
}
|
||||
|
||||
fn add_owner_route(&self, id: Ipv4Addr, key: RouteKey) {
|
||||
let route = Route::from_default_rt(key, 1);
|
||||
let mut guard = self.route_table.write();
|
||||
|
||||
self.route_key_owner.lock().insert(key, id);
|
||||
self.route_key_time.lock().insert((id, key), Instant::now());
|
||||
|
||||
let list = guard.entry(id).or_insert_with(|| Vec::with_capacity(6));
|
||||
if list.iter().any(|v| v.route_key() == key) {
|
||||
return;
|
||||
}
|
||||
list.push(route);
|
||||
}
|
||||
|
||||
fn add_route(&self, id: Ipv4Addr, route: Route) {
|
||||
let key = route.route_key();
|
||||
let mut guard = self.route_table.write();
|
||||
|
||||
// 检查是否是 owner 路由
|
||||
let mut route_key_owner = self.route_key_owner.lock();
|
||||
if route.is_direct() {
|
||||
route_key_owner.entry(key).or_insert(id);
|
||||
} else {
|
||||
if !guard.contains_key(&id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
self.route_key_time.lock().insert((id, key), Instant::now());
|
||||
|
||||
let list = guard.entry(id).or_insert_with(|| Vec::with_capacity(6));
|
||||
|
||||
// 如果路由已存��,更新并重新排序
|
||||
if let Some(idx) = list.iter().position(|v| v.route_key() == key) {
|
||||
list[idx] = route;
|
||||
// 向前冒泡(如果 RTT 更小)
|
||||
let mut i = idx;
|
||||
while i > 0 && list[i].rtt() < list[i - 1].rtt() {
|
||||
list.swap(i, i - 1);
|
||||
i -= 1;
|
||||
}
|
||||
// 向后冒泡(如果 RTT 更大)
|
||||
while i + 1 < list.len() && list[i].rtt() > list[i + 1].rtt() {
|
||||
list.swap(i, i + 1);
|
||||
i += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 插入新路由,保持按 RTT 排序
|
||||
let mut pos = list.len();
|
||||
for (i, r) in list.iter().enumerate() {
|
||||
if route.rtt() < r.rtt() {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.insert(pos, route);
|
||||
}
|
||||
|
||||
fn remove_oldest_route(&self, expired_time: Instant) {
|
||||
let mut expired_keys = Vec::new();
|
||||
{
|
||||
let mut time_map = self.route_key_time.lock();
|
||||
time_map.retain(|(id, route_key), t| {
|
||||
if *t <= expired_time {
|
||||
expired_keys.push((*id, *route_key));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if expired_keys.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut table = self.route_table.write();
|
||||
let mut owner_map = self.route_key_owner.lock();
|
||||
|
||||
for (id, route_key) in expired_keys {
|
||||
if let Some(list) = table.get_mut(&id) {
|
||||
list.retain(|r| r.route_key() != route_key);
|
||||
if list.is_empty() {
|
||||
table.remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(owner_id) = owner_map.get(&route_key) {
|
||||
if *owner_id == id {
|
||||
owner_map.remove(&route_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub(crate) mod nat_test;
|
||||
pub(crate) mod punch;
|
||||
|
||||
pub(crate) mod task;
|
||||
@@ -0,0 +1,387 @@
|
||||
use crate::context::AppState;
|
||||
use rust_p2p_core::nat::{NatInfo, NatType};
|
||||
use rust_p2p_core::tunnel::SocketManager;
|
||||
use rust_p2p_core::tunnel::udp::Model;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
pub async fn my_nat_info(app_context: AppState, socket_manager: SocketManager) {
|
||||
loop {
|
||||
my_nat_info_impl(&app_context, &socket_manager).await;
|
||||
tokio::time::sleep(Duration::from_secs(60 * 30)).await;
|
||||
}
|
||||
}
|
||||
async fn my_nat_info_impl(app_context: &AppState, socket_manager: &SocketManager) {
|
||||
let network = app_context.network.network();
|
||||
let mut local_ipv4s = Vec::new();
|
||||
let mut local_ipv6 = Vec::new();
|
||||
match getifaddrs::getifaddrs() {
|
||||
Ok(addrs) => {
|
||||
for x in addrs {
|
||||
let Some(ip) = x.address.ip_addr() else {
|
||||
continue;
|
||||
};
|
||||
if ip.is_loopback() {
|
||||
continue;
|
||||
}
|
||||
if ip.is_unspecified() {
|
||||
continue;
|
||||
}
|
||||
if ip.is_multicast() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match ip {
|
||||
IpAddr::V4(addr) => {
|
||||
if addr.is_documentation() {
|
||||
continue;
|
||||
}
|
||||
if addr.is_broadcast() {
|
||||
continue;
|
||||
}
|
||||
if let Some(network) = &network
|
||||
&& network.contains(&addr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
local_ipv4s.push(addr);
|
||||
}
|
||||
IpAddr::V6(addr) => {
|
||||
if addr.is_unique_local() {
|
||||
continue;
|
||||
}
|
||||
if addr.is_unicast_link_local() {
|
||||
continue;
|
||||
}
|
||||
local_ipv6.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("getifaddrs error: {e}");
|
||||
}
|
||||
}
|
||||
log::info!("local_ipv4s: {:?}", local_ipv4s);
|
||||
let local_ipv4 = rust_p2p_core::extend::addr::local_ipv4()
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("local ipv4 failed {e:?}");
|
||||
local_ipv4s
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(Ipv4Addr::UNSPECIFIED)
|
||||
});
|
||||
local_ipv4s = vec![local_ipv4];
|
||||
let mut ipv6 = rust_p2p_core::extend::addr::local_ipv6().await.ok();
|
||||
if let Some(addr) = ipv6 {
|
||||
if addr.is_loopback()
|
||||
|| addr.is_unique_local()
|
||||
|| addr.is_unicast_link_local()
|
||||
|| addr.is_unspecified()
|
||||
|| addr.is_multicast()
|
||||
{
|
||||
ipv6 = local_ipv6.first().cloned();
|
||||
}
|
||||
} else {
|
||||
ipv6 = local_ipv6.first().cloned();
|
||||
}
|
||||
let local_udp_ports = socket_manager
|
||||
.udp_socket_manager_as_ref()
|
||||
.unwrap()
|
||||
.local_ports()
|
||||
.unwrap();
|
||||
let local_tcp_port = socket_manager
|
||||
.tcp_socket_manager_as_ref()
|
||||
.unwrap()
|
||||
.local_addr()
|
||||
.port();
|
||||
log::info!(
|
||||
"local_ipv4={local_ipv4},ipv6={ipv6:?},local_udp_ports:{local_udp_ports:?},local_tcp_port:{local_tcp_port:?}"
|
||||
);
|
||||
let mut public_ports = local_udp_ports.clone();
|
||||
public_ports.fill(0);
|
||||
let mut nat_info = NatInfo {
|
||||
nat_type: NatType::Cone,
|
||||
public_ips: vec![],
|
||||
public_udp_ports: public_ports,
|
||||
mapping_tcp_addr: vec![],
|
||||
mapping_udp_addr: vec![],
|
||||
public_port_range: 0,
|
||||
local_ipv4s,
|
||||
local_ipv4,
|
||||
ipv6,
|
||||
local_udp_ports,
|
||||
local_tcp_port,
|
||||
public_tcp_port: 0,
|
||||
};
|
||||
let mut stun_server = app_context.udp_stun();
|
||||
if stun_server.is_empty() {
|
||||
stun_server = default_udp_stun();
|
||||
}
|
||||
let (nat_type, public_ips, port_range) = rust_p2p_core::stun::stun_test_nat(stun_server, None)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("stun_test_nat {e:?}");
|
||||
(NatType::Cone, vec![], 0)
|
||||
});
|
||||
log::info!("nat_type:{nat_type:?},public_ips:{public_ips:?},port_range={port_range}");
|
||||
nat_info.nat_type = nat_type;
|
||||
nat_info.public_ips = public_ips;
|
||||
nat_info.public_port_range = port_range;
|
||||
app_context.nat_info.replace_nat_info(nat_info);
|
||||
let model = match nat_type {
|
||||
NatType::Cone => Model::Low,
|
||||
NatType::Symmetric => Model::High,
|
||||
};
|
||||
if let Err(e) = socket_manager
|
||||
.udp_socket_manager_as_ref()
|
||||
.unwrap()
|
||||
.switch_model(model)
|
||||
{
|
||||
log::error!("switch_model error: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_udp_public_addr_loop(app_context: AppState, socket_manager: SocketManager) {
|
||||
let mut udp_stun_servers = app_context.udp_stun();
|
||||
if udp_stun_servers.is_empty() {
|
||||
udp_stun_servers = default_udp_stun();
|
||||
}
|
||||
let udp_len = udp_stun_servers.len();
|
||||
let mut udp_count = 0;
|
||||
let stun_request = rust_p2p_core::stun::send_stun_request();
|
||||
loop {
|
||||
let stun = &udp_stun_servers[udp_count % udp_len];
|
||||
udp_count += 1;
|
||||
match tokio::net::lookup_host(stun.as_str()).await {
|
||||
Ok(mut addr) => {
|
||||
if let Some(addr) = addr.next()
|
||||
&& let Some(w) = socket_manager.udp_socket_manager_as_ref()
|
||||
&& let Err(e) = w.detect_pub_addrs(&stun_request, addr).await
|
||||
{
|
||||
log::info!("detect_pub_addrs {e:?} {addr:?}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::info!("query_public_addr lookup_host {e:?} {stun:?}",);
|
||||
}
|
||||
}
|
||||
let not_port = app_context
|
||||
.get_nat_info()
|
||||
.map(|v| v.public_udp_ports.contains(&0))
|
||||
.unwrap_or(true);
|
||||
if not_port {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
} else {
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn query_tcp_public_addr_loop(
|
||||
app_context: AppState,
|
||||
socket_manager: SocketManager,
|
||||
) {
|
||||
use rand::Rng;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
let tcp_stun_servers = {
|
||||
let servers = app_context.tcp_stun();
|
||||
if servers.is_empty() {
|
||||
default_tcp_stun()
|
||||
} else {
|
||||
servers
|
||||
}
|
||||
};
|
||||
|
||||
if tcp_stun_servers.is_empty() {
|
||||
return;
|
||||
}
|
||||
log::debug!("tcp_stun_servers = {tcp_stun_servers:?}");
|
||||
|
||||
let stun_request = rust_p2p_core::stun::send_stun_request();
|
||||
let target_conn_count = tcp_stun_servers.len().min(2);
|
||||
let mut active_connections: HashMap<SocketAddr, (TcpStream, SocketAddr)> = HashMap::new();
|
||||
|
||||
'outer: loop {
|
||||
while active_connections.len() < target_conn_count {
|
||||
let mut candidates: Vec<&String> = tcp_stun_servers.iter().collect();
|
||||
candidates.shuffle(&mut rand::rng());
|
||||
|
||||
let mut connected = false;
|
||||
for stun in candidates {
|
||||
let addr = match tokio::net::lookup_host(stun.as_str()).await {
|
||||
Ok(mut addrs) => addrs.next(),
|
||||
Err(e) => {
|
||||
log::debug!("lookup_host failed {stun} {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(addr) = addr else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if active_connections.contains_key(&addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(w) = socket_manager.tcp_socket_manager_as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(5), w.connect_reuse_port_raw(addr))
|
||||
.await
|
||||
{
|
||||
Ok(Ok(mut tcp_stream)) => {
|
||||
let write_result = tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
tcp_stream.write_all(&stun_request),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(Ok(_)) = write_result {
|
||||
match stun_tcp_read(&mut tcp_stream).await {
|
||||
Ok(pub_addr) => {
|
||||
log::debug!(
|
||||
"update_tcp_public_addr {stun} {addr} -> {pub_addr}"
|
||||
);
|
||||
|
||||
let existing_pub_addr =
|
||||
active_connections.values().next().map(|(_, p)| *p);
|
||||
|
||||
if let Some(existing) = existing_pub_addr
|
||||
&& existing != pub_addr
|
||||
{
|
||||
log::debug!(
|
||||
"pub_addr mismatch: {existing} != {pub_addr}, wait 60s"
|
||||
);
|
||||
active_connections.clear();
|
||||
app_context.nat_info.update_tcp_public_addr(
|
||||
SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into(),
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(5 * 60)).await;
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
active_connections.insert(addr, (tcp_stream, pub_addr));
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("stun_tcp_read failed {stun} {addr} {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("write stun request failed {stun} {addr}");
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::debug!("connect_reuse_port_raw failed {stun} {addr} {e}");
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("connect_reuse_port_raw timeout {stun} {addr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !connected {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let existing_pub_addr = active_connections.values().next().map(|(_, p)| *p);
|
||||
if let Some(existing) = existing_pub_addr {
|
||||
app_context.nat_info.update_tcp_public_addr(existing);
|
||||
}
|
||||
|
||||
let sleep_secs = rand::rng().random_range(10u64..=15);
|
||||
tokio::time::sleep(Duration::from_secs(sleep_secs)).await;
|
||||
|
||||
let mut to_remove = Vec::new();
|
||||
let addrs: Vec<SocketAddr> = active_connections.keys().cloned().collect();
|
||||
|
||||
for addr in addrs {
|
||||
let (tcp_stream, _) = active_connections.get_mut(&addr).unwrap();
|
||||
let mut buf = [0u8; 1024];
|
||||
|
||||
match tcp_stream.try_read(&mut buf) {
|
||||
Ok(0) => {
|
||||
log::warn!("stun tcp close {addr} EOF");
|
||||
to_remove.push(addr);
|
||||
continue;
|
||||
}
|
||||
Err(e) if e.kind() != std::io::ErrorKind::WouldBlock => {
|
||||
log::warn!("stun tcp read error {addr} {e}");
|
||||
to_remove.push(addr);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(3), tcp_stream.write_all(&stun_request))
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {}
|
||||
Ok(Err(e)) => {
|
||||
log::warn!("stun tcp write error {addr} {e}");
|
||||
to_remove.push(addr);
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("stun tcp write timeout {addr}");
|
||||
to_remove.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for addr in to_remove {
|
||||
active_connections.remove(&addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn stun_tcp_read(tcp_stream: &mut TcpStream) -> io::Result<SocketAddr> {
|
||||
let mut head = [0; 20];
|
||||
match tokio::time::timeout(Duration::from_secs(5), tcp_stream.read_exact(&mut head)).await {
|
||||
Ok(rs) => rs?,
|
||||
Err(_) => Err(io::Error::from(io::ErrorKind::TimedOut))?,
|
||||
};
|
||||
let len = u16::from_be_bytes([head[2], head[3]]) as usize;
|
||||
let mut buf = vec![0; len + 20];
|
||||
buf[..20].copy_from_slice(&head);
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
tcp_stream.read_exact(&mut buf[20..]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(rs) => rs?,
|
||||
Err(_) => Err(io::Error::from(io::ErrorKind::TimedOut))?,
|
||||
};
|
||||
if let Some(addr) = rust_p2p_core::stun::recv_stun_response(&buf) {
|
||||
Ok(addr)
|
||||
} else {
|
||||
log::debug!("stun_tcp_read {buf:?}");
|
||||
Err(io::Error::from(io::ErrorKind::InvalidData))
|
||||
}
|
||||
}
|
||||
|
||||
fn default_udp_stun() -> Vec<String> {
|
||||
vec![
|
||||
"stun.miwifi.com:3478".to_string(),
|
||||
"stun.chat.bilibili.com:3478".to_string(),
|
||||
"stun.l.google.com:19302".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_tcp_stun() -> Vec<String> {
|
||||
vec![
|
||||
"stun.flashdance.cx:3478".to_string(),
|
||||
"stun.sipnet.net:3478".to_string(),
|
||||
"stun.nextcloud.com:443".to_string(),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use crate::context::nat::PunchBackoff;
|
||||
use crate::context::{ServerInfoCollection, SharedNetworkAddr};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::protocol::client_message::PunchInfo;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::route_table::RouteTable;
|
||||
use crate::tunnel_core::server::outbound::ServerOutbound;
|
||||
use anyhow::bail;
|
||||
use log::error;
|
||||
use rand::seq::SliceRandom;
|
||||
use rust_p2p_core::punch::{PunchModel, Puncher};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct PunchTaskContext {
|
||||
pub network: SharedNetworkAddr,
|
||||
pub server_info: ServerInfoCollection,
|
||||
pub punch_backoff: PunchBackoff,
|
||||
pub punch_info_getter: PunchInfoGetter,
|
||||
}
|
||||
|
||||
pub type PunchInfoGetter = std::sync::Arc<dyn Fn() -> Option<PunchInfo> + Send + Sync>;
|
||||
|
||||
pub async fn punch_task(
|
||||
tunnel_to_server: ServerOutbound,
|
||||
route_table: RouteTable,
|
||||
ctx: PunchTaskContext,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
let Some(src_ip) = ctx.network.ip() else {
|
||||
continue;
|
||||
};
|
||||
let Some(punch_info) = (ctx.punch_info_getter)() else {
|
||||
continue;
|
||||
};
|
||||
let mut list = ctx.server_info.client_online_ips();
|
||||
list.shuffle(&mut rand::rng());
|
||||
list.truncate(5);
|
||||
for dest_ip in list {
|
||||
if dest_ip <= src_ip {
|
||||
continue;
|
||||
}
|
||||
if ctx.server_info.is_any_server_connected(None) && route_table.need_punch(&dest_ip) {
|
||||
if !ctx.punch_backoff.should_punch(dest_ip) {
|
||||
continue;
|
||||
}
|
||||
log::info!("punching {dest_ip}");
|
||||
|
||||
let data = punch_info.encode();
|
||||
let mut net_packet = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + data.len(),
|
||||
tunnel_to_server.encrypt_reserve(),
|
||||
))?;
|
||||
net_packet.set_msg_type(MsgType::PunchStart1);
|
||||
net_packet.set_ttl(2);
|
||||
net_packet.set_src_id(src_ip.into());
|
||||
net_packet.set_dest_id(dest_ip.into());
|
||||
net_packet.set_payload(data.as_ref())?;
|
||||
if let Err(e) = tunnel_to_server.send(dest_ip, net_packet).await {
|
||||
error!("punch send error {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct NatPuncher {
|
||||
network: SharedNetworkAddr,
|
||||
punch_backoff: PunchBackoff,
|
||||
puncher: Option<Puncher>,
|
||||
packet_crypto: PacketCrypto,
|
||||
}
|
||||
|
||||
impl NatPuncher {
|
||||
pub fn new(
|
||||
network: SharedNetworkAddr,
|
||||
punch_backoff: PunchBackoff,
|
||||
puncher: Option<Puncher>,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> Self {
|
||||
Self {
|
||||
network,
|
||||
punch_backoff,
|
||||
puncher,
|
||||
packet_crypto,
|
||||
}
|
||||
}
|
||||
pub fn punch(&self, dest_ip: Ipv4Addr, punch_info: PunchInfo) -> anyhow::Result<bool> {
|
||||
if self.puncher.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
if !self.punch_backoff.should_punch(dest_ip) {
|
||||
return Ok(false);
|
||||
}
|
||||
self.punch_uncheck_delay(dest_ip, punch_info, Some(Duration::from_millis(50)))?;
|
||||
Ok(true)
|
||||
}
|
||||
pub fn punch_uncheck(&self, dest_ip: Ipv4Addr, punch_info: PunchInfo) -> anyhow::Result<()> {
|
||||
self.punch_uncheck_delay(dest_ip, punch_info, None)
|
||||
}
|
||||
pub fn punch_uncheck_delay(
|
||||
&self,
|
||||
dest_ip: Ipv4Addr,
|
||||
punch_info: PunchInfo,
|
||||
time: Option<Duration>,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(puncher) = self.puncher.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(src_ip) = self.network.ip() else {
|
||||
bail!("not ip");
|
||||
};
|
||||
let packet_crypto = self.packet_crypto.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Some(time) = time {
|
||||
tokio::time::sleep(time).await;
|
||||
}
|
||||
if let Err(e) = punch_now(puncher, src_ip, dest_ip, punch_info, packet_crypto).await {
|
||||
log::warn!("punch send error {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn punch_now(
|
||||
puncher: Puncher,
|
||||
src_ip: Ipv4Addr,
|
||||
dest_ip: Ipv4Addr,
|
||||
nat_info: PunchInfo,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + 8,
|
||||
packet_crypto.encrypt_reserve(),
|
||||
))?;
|
||||
packet.set_msg_type(MsgType::PunchReq);
|
||||
packet.set_ttl(1);
|
||||
packet.set_src_id(src_ip.into());
|
||||
packet.set_dest_id(dest_ip.into());
|
||||
packet.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())?;
|
||||
packet_crypto.encrypt_in_place(&mut packet)?;
|
||||
let buf = packet.buffer();
|
||||
let punch_info = rust_p2p_core::punch::PunchInfo::new(PunchModel::all(), nat_info.nat_info);
|
||||
puncher.punch_now(Some(buf), buf, punch_info).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
use crate::context::nat::MyNatInfo;
|
||||
use crate::context::{AppState, PacketLossStats, SharedNetworkAddr};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::inbound::P2pInboundHandler;
|
||||
use crate::tunnel_core::p2p::outbound::P2pOutbound;
|
||||
use crate::tunnel_core::p2p::route_table::RouteTable;
|
||||
use crate::tunnel_core::p2p::transport::nat_test::{
|
||||
my_nat_info, query_tcp_public_addr_loop, query_udp_public_addr_loop,
|
||||
};
|
||||
use crate::tunnel_core::p2p::transport::punch::{PunchTaskContext, punch_task};
|
||||
use crate::tunnel_core::server::outbound::ServerOutbound;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use rust_p2p_core::punch::Puncher;
|
||||
use rust_p2p_core::tunnel::{Tunnel, TunnelDispatcher, new_tunnel_component};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub async fn init_tunnel(
|
||||
task_group: TaskGroup,
|
||||
app_state: AppState,
|
||||
tunnel_to_server: ServerOutbound,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> anyhow::Result<(Puncher, P2pOutbound, P2pTask)> {
|
||||
let udp_config = rust_p2p_core::tunnel::config::UdpTunnelConfig::default()
|
||||
.set_main_udp_count(2)
|
||||
.set_sub_udp_count(82);
|
||||
let tcp_config = rust_p2p_core::tunnel::config::TcpTunnelConfig::new(Box::new(
|
||||
rust_p2p_core::tunnel::tcp::LengthPrefixedInitCodec,
|
||||
))
|
||||
.set_tcp_multiplexing_limit(2);
|
||||
let config = rust_p2p_core::tunnel::config::TunnelConfig::empty()
|
||||
.set_udp_tunnel_config(udp_config)
|
||||
.set_tcp_tunnel_config(tcp_config);
|
||||
let (tunnel_dispatcher, puncher) = new_tunnel_component(config)?;
|
||||
let route_table = app_state.route_table.clone();
|
||||
let socket_manager = P2pOutbound::new(
|
||||
tunnel_dispatcher.socket_manager(),
|
||||
route_table.clone(),
|
||||
packet_crypto,
|
||||
);
|
||||
task_group.spawn(my_nat_info(
|
||||
app_state.clone(),
|
||||
tunnel_dispatcher.socket_manager(),
|
||||
));
|
||||
let manager = tunnel_dispatcher.socket_manager();
|
||||
task_group.spawn(query_udp_public_addr_loop(
|
||||
app_state.clone(),
|
||||
manager.clone(),
|
||||
));
|
||||
task_group.spawn(query_tcp_public_addr_loop(app_state.clone(), manager));
|
||||
|
||||
task_group.spawn(route_timeout_task(route_table.clone()));
|
||||
let app_state_for_punch = app_state.clone();
|
||||
let punch_ctx = PunchTaskContext {
|
||||
network: app_state.network.clone(),
|
||||
server_info: app_state.server_info_collection.clone(),
|
||||
punch_backoff: app_state.punch_backoff.clone(),
|
||||
punch_info_getter: Arc::new(move || app_state_for_punch.get_punch_info()),
|
||||
};
|
||||
task_group.spawn(punch_task(tunnel_to_server, route_table.clone(), punch_ctx));
|
||||
task_group.spawn(ping_all(
|
||||
app_state.network.clone(),
|
||||
app_state.packet_loss_stats.clone(),
|
||||
route_table.clone(),
|
||||
socket_manager.clone(),
|
||||
));
|
||||
task_group.spawn(relay_probe_task(
|
||||
app_state.network.clone(),
|
||||
app_state.server_info_collection.clone(),
|
||||
route_table.clone(),
|
||||
socket_manager.clone(),
|
||||
));
|
||||
let p2p_task = P2pTask {
|
||||
task_group,
|
||||
nat_info: app_state.nat_info.clone(),
|
||||
socket_manager: socket_manager.clone(),
|
||||
tunnel_dispatcher,
|
||||
};
|
||||
Ok((puncher, socket_manager, p2p_task))
|
||||
}
|
||||
pub struct P2pTask {
|
||||
task_group: TaskGroup,
|
||||
nat_info: MyNatInfo,
|
||||
socket_manager: P2pOutbound,
|
||||
tunnel_dispatcher: TunnelDispatcher,
|
||||
}
|
||||
impl P2pTask {
|
||||
pub fn start(self, p2p_inbound_handler: P2pInboundHandler) {
|
||||
self.task_group.spawn(tunnel_dispatch_task(
|
||||
self.nat_info,
|
||||
self.task_group.clone(),
|
||||
self.tunnel_dispatcher,
|
||||
p2p_inbound_handler,
|
||||
self.socket_manager,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ping_all(
|
||||
network: SharedNetworkAddr,
|
||||
packet_loss_stats: PacketLossStats,
|
||||
route_table: RouteTable,
|
||||
socket_manager: P2pOutbound,
|
||||
) {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
let Some(src) = network.ip() else {
|
||||
continue;
|
||||
};
|
||||
let vec = route_table.route_table();
|
||||
|
||||
for (id, list) in vec {
|
||||
for (index, route) in list.iter().enumerate() {
|
||||
if index > 2 {
|
||||
break;
|
||||
}
|
||||
let Ok(mut ping) = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + 8,
|
||||
socket_manager.encrypt_reserve(),
|
||||
)) else {
|
||||
continue;
|
||||
};
|
||||
ping.set_msg_type(MsgType::Ping);
|
||||
ping.set_ttl(1);
|
||||
ping.set_src_id(src.into());
|
||||
ping.set_dest_id(id.into());
|
||||
ping.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())
|
||||
.unwrap();
|
||||
if socket_manager
|
||||
.send_to(ping, &route.route_key())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
packet_loss_stats.record_sent(id);
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn route_timeout_task(route_table: RouteTable) {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
let expired_time = std::time::Instant::now() - Duration::from_secs(10);
|
||||
route_table.remove_oldest_route(expired_time);
|
||||
}
|
||||
}
|
||||
|
||||
/// 客户端中继探测任务
|
||||
/// 每5分钟执行一次,找到所有未直连的目标IP,通过Ping消息发送给已打洞的客户端
|
||||
pub async fn relay_probe_task(
|
||||
network: SharedNetworkAddr,
|
||||
server_info: crate::context::ServerInfoCollection,
|
||||
route_table: RouteTable,
|
||||
socket_manager: P2pOutbound,
|
||||
) {
|
||||
use rand::prelude::*;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(300)).await; // 5分钟
|
||||
|
||||
let Some(src) = network.ip() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let online_ips = server_info.client_online_ips();
|
||||
if online_ips.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut non_direct_targets = Vec::new();
|
||||
for ip in online_ips {
|
||||
if ip == src {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_direct = route_table
|
||||
.get_route_by_id(&ip)
|
||||
.ok()
|
||||
.map(|route| route.is_direct())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_direct {
|
||||
non_direct_targets.push(ip);
|
||||
if non_direct_targets.len() >= 20 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if non_direct_targets.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let non_direct_count = non_direct_targets.len();
|
||||
|
||||
let targets_to_probe: Vec<Ipv4Addr> = {
|
||||
let mut rng = rand::rng();
|
||||
if non_direct_targets.len() <= 10 {
|
||||
non_direct_targets
|
||||
} else {
|
||||
non_direct_targets
|
||||
.choose_multiple(&mut rng, 10)
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let all_routes = route_table.route_table();
|
||||
let mut direct_peers = Vec::new();
|
||||
for (ip, routes) in &all_routes {
|
||||
if let Some(best_route) = routes.first() {
|
||||
if best_route.is_direct() {
|
||||
direct_peers.push((*ip, best_route.route_key()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if direct_peers.is_empty() {
|
||||
log::debug!("No direct peers available for relay probe");
|
||||
continue;
|
||||
}
|
||||
|
||||
let max_probes_per_target = 3.min(direct_peers.len());
|
||||
|
||||
for target_ip in &targets_to_probe {
|
||||
let selected_peers: Vec<_> = {
|
||||
let mut rng = rand::rng();
|
||||
direct_peers
|
||||
.iter()
|
||||
.filter(|(ip, _)| ip != target_ip)
|
||||
.choose_multiple(&mut rng, max_probes_per_target)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
|
||||
for (relay_ip, route_key) in selected_peers {
|
||||
// 构造Ping消息,目标是target_ip,但发送给relay_ip
|
||||
let Ok(mut ping) = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + 8,
|
||||
socket_manager.encrypt_reserve(),
|
||||
)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
ping.set_msg_type(MsgType::Ping);
|
||||
ping.set_ttl(2); // TTL设为2,允许中继一次
|
||||
ping.set_src_id(src.into());
|
||||
ping.set_dest_id((*target_ip).into());
|
||||
ping.set_payload(&crate::utils::time::now_ts_ms().to_be_bytes())
|
||||
.unwrap();
|
||||
|
||||
// 发送给已打洞的客户端,让它中继到目标
|
||||
if let Err(e) = socket_manager.send_to(ping, &route_key).await {
|
||||
log::debug!(
|
||||
"Failed to send relay probe to {} for target {}: {:?}",
|
||||
relay_ip,
|
||||
target_ip,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 控制发送速率,避免网络拥塞
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Relay probe task completed: {} targets probed (from {} non-direct), {} direct peers",
|
||||
targets_to_probe.len(),
|
||||
non_direct_count,
|
||||
direct_peers.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 隧道收发调度与数据分发
|
||||
pub async fn tunnel_dispatch_task(
|
||||
nat_info: MyNatInfo,
|
||||
task_group: TaskGroup,
|
||||
mut tunnel_factory: TunnelDispatcher,
|
||||
p2p_inbound_handler: P2pInboundHandler,
|
||||
p2p_socket_manager: P2pOutbound,
|
||||
) {
|
||||
loop {
|
||||
let mut tunnel = match tunnel_factory.dispatch().await {
|
||||
Ok(rs) => rs,
|
||||
Err(e) => {
|
||||
log::error!("tunnel disptach :{e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
log::info!("tunnel {:?}-{:?}", tunnel.protocol(), tunnel.remote_addr());
|
||||
let p2p_inbound_handler = p2p_inbound_handler.clone();
|
||||
let nat_info = nat_info.clone();
|
||||
let p2p_socket_manager = p2p_socket_manager.clone();
|
||||
task_group.spawn(async move {
|
||||
let mut buf = vec![0; 65536];
|
||||
while let Some(rs) = tunnel.recv_from(&mut buf).await {
|
||||
let (len, route_key) = match rs {
|
||||
Ok(rs) => rs,
|
||||
Err(e) => {
|
||||
log::warn!("recv_from {e:?}");
|
||||
if tunnel.protocol().is_udp() {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
if tunnel.protocol().is_udp()
|
||||
&& rust_p2p_core::stun::is_stun_response(&buf[..len])
|
||||
&& let Some(pub_addr) = rust_p2p_core::stun::recv_stun_response(&buf[..len])
|
||||
{
|
||||
nat_info.update_public_addr(route_key.index(), pub_addr);
|
||||
continue;
|
||||
}
|
||||
let mut bytes = TransmissionBytes::zeroed(len);
|
||||
bytes.copy_from_slice(&buf[..len]);
|
||||
p2p_inbound_handler
|
||||
.next_handle(bytes, route_key, &p2p_socket_manager, &mut tunnel)
|
||||
.await;
|
||||
}
|
||||
log::info!(
|
||||
"drop tunnel {:?}-{:?}",
|
||||
tunnel.protocol(),
|
||||
tunnel.remote_addr()
|
||||
);
|
||||
if let Tunnel::Tcp(tcp) = tunnel {
|
||||
p2p_inbound_handler.tcp_disconnect(tcp.route_key()).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
use crate::compression::PacketCompression;
|
||||
use crate::context::config::Config;
|
||||
use crate::context::nat::{MyNatInfo, PunchBackoff};
|
||||
use crate::context::{AppState, NetworkAddr, NetworkRoute, PeerInfoMap, ServerInfoCollection};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||
use crate::fec::FecDecoder;
|
||||
use crate::protocol::control_message::{
|
||||
ConfirmRegResponseMsg, RegResponseMsg, RegistrationMode, RequestMessage, ResponseMessage,
|
||||
};
|
||||
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
|
||||
use crate::tunnel_core::server::inbound::ServerTurnInboundHandler;
|
||||
use crate::tunnel_core::server::outbound::ServerOutbound;
|
||||
use crate::tunnel_core::server::rpc::{RpcNotifier, ServerRPC};
|
||||
use crate::tunnel_core::server::transport::TransportClient;
|
||||
use crate::tunnel_core::server::transport::config::ConnectRegConfig;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::bail;
|
||||
use bytes::Bytes;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub struct InboundHandlerConfig {
|
||||
pub network_route: NetworkRoute,
|
||||
pub server_info: ServerInfoCollection,
|
||||
pub nat_info: MyNatInfo,
|
||||
pub peer_map: PeerInfoMap,
|
||||
pub punch_backoff: PunchBackoff,
|
||||
pub puncher: NatPuncher,
|
||||
pub packet_crypto: PacketCrypto,
|
||||
pub packet_compression: PacketCompression,
|
||||
pub enhanced_inbound: EnhancedInbound,
|
||||
pub fec_decoder: FecDecoder,
|
||||
}
|
||||
|
||||
pub struct ServerTurnManager {
|
||||
server_id: u32,
|
||||
config: ConnectRegConfig,
|
||||
receiver: Option<Receiver<(Bytes, Instant)>>,
|
||||
notifier: RpcNotifier,
|
||||
transport_client: TransportClient,
|
||||
}
|
||||
pub(crate) fn create_server_tunnel(
|
||||
app_state: AppState,
|
||||
config: &Config,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> (Vec<ServerTurnManager>, ServerOutbound, ServerRPC) {
|
||||
let mut rpc_notifier: HashMap<u32, RpcNotifier> = HashMap::new();
|
||||
let mut sender_map: HashMap<u32, Sender<(Bytes, Instant)>> = HashMap::new();
|
||||
let mut server_manager_list = Vec::with_capacity(config.server_addr.len());
|
||||
let mut server_addr_list = Vec::with_capacity(config.server_addr.len());
|
||||
for (index, server_addr) in config.server_addr.iter().enumerate() {
|
||||
let connect_reg_config = config.to_connect_config(index);
|
||||
|
||||
let server_id = index as u32;
|
||||
|
||||
let (s, r) = tokio::sync::mpsc::channel(1024);
|
||||
|
||||
let notifier = RpcNotifier::new();
|
||||
let manager =
|
||||
ServerTurnManager::new(server_id, connect_reg_config.clone(), r, notifier.clone());
|
||||
server_addr_list.push((server_id, server_addr.clone()));
|
||||
rpc_notifier.insert(server_id, notifier);
|
||||
sender_map.insert(server_id, s);
|
||||
server_manager_list.push(manager);
|
||||
}
|
||||
let server_info_collection = app_state.server_info_collection.clone();
|
||||
server_info_collection.update_server(server_addr_list);
|
||||
let tunnel_to_server =
|
||||
ServerOutbound::new(Arc::new(sender_map), server_info_collection, packet_crypto);
|
||||
|
||||
let server_rpc = ServerRPC::new(tunnel_to_server.clone(), rpc_notifier);
|
||||
|
||||
(server_manager_list, tunnel_to_server, server_rpc)
|
||||
}
|
||||
|
||||
impl ServerTurnManager {
|
||||
pub fn new(
|
||||
server_id: u32,
|
||||
config: ConnectRegConfig,
|
||||
receiver: Receiver<(Bytes, Instant)>,
|
||||
notifier: RpcNotifier,
|
||||
) -> Self {
|
||||
let connector = TransportClient::new();
|
||||
Self {
|
||||
server_id,
|
||||
transport_client: connector,
|
||||
config,
|
||||
receiver: Some(receiver),
|
||||
notifier,
|
||||
}
|
||||
}
|
||||
pub fn disconnect(&mut self) {
|
||||
self.transport_client.disconnect();
|
||||
}
|
||||
|
||||
pub async fn connect_and_reg(
|
||||
&mut self,
|
||||
mode: RegistrationMode,
|
||||
) -> anyhow::Result<ResponseMessage> {
|
||||
let connect_config = self.config.to_connect_config().await?;
|
||||
log::info!(
|
||||
"Connecting to server[{}] {:?} with mode {:?}",
|
||||
self.server_id,
|
||||
connect_config,
|
||||
mode,
|
||||
);
|
||||
|
||||
self.transport_client
|
||||
.connect_timeout(&connect_config, Duration::from_secs(10))
|
||||
.await?;
|
||||
|
||||
let reg_msg = self.config.reg_msg_request(self.server_id, mode);
|
||||
let request_msg = RequestMessage::Reg(reg_msg);
|
||||
let encoded = request_msg.encode();
|
||||
|
||||
self.transport_client
|
||||
.send(encoded.freeze())
|
||||
.await?;
|
||||
let buf = self
|
||||
.transport_client
|
||||
.next_timeout(Duration::from_secs(10))
|
||||
.await?;
|
||||
let response = ResponseMessage::from_slice(&buf)?;
|
||||
match &response {
|
||||
ResponseMessage::Reg(_) => {}
|
||||
ResponseMessage::Error(_e) => {
|
||||
self.disconnect();
|
||||
}
|
||||
ResponseMessage::ConfirmReg(_) => {
|
||||
self.disconnect();
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn send_confirm(&mut self) -> anyhow::Result<ConfirmRegResponseMsg> {
|
||||
self.transport_client
|
||||
.send(RequestMessage::ConfirmReg.encode().freeze())
|
||||
.await?;
|
||||
let buf = self
|
||||
.transport_client
|
||||
.next_timeout(Duration::from_secs(10))
|
||||
.await?;
|
||||
let response = ResponseMessage::from_slice(&buf)?;
|
||||
match response {
|
||||
ResponseMessage::ConfirmReg(msg) => Ok(msg),
|
||||
ResponseMessage::Error(e) => bail!("Confirm failed: {}", e.message),
|
||||
_ => bail!("Unexpected response"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ip(&mut self, ip: Ipv4Addr) {
|
||||
self.config.ip = Some(ip);
|
||||
}
|
||||
|
||||
/// Start data handling task with an already established connection.
|
||||
pub fn data_handle_task_connected(
|
||||
mut self,
|
||||
task_group: &TaskGroup,
|
||||
config: Box<InboundHandlerConfig>,
|
||||
initial_response: NetworkAddr,
|
||||
) {
|
||||
let data_handler =
|
||||
ServerTurnInboundHandler::new(self.server_id, initial_response, config);
|
||||
let task_group_ = task_group.clone();
|
||||
let Some(mut receiver) = self.receiver.take() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
task_group.spawn(async move {
|
||||
let mut already_connected = true;
|
||||
loop {
|
||||
if !already_connected {
|
||||
self.disconnect();
|
||||
data_handler.handle_disconnected();
|
||||
let msg = match self.connect_and_reg(RegistrationMode::Normal).await {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
log::error!("连接服务器失败:{e:?}");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match &msg {
|
||||
ResponseMessage::Reg(reg) => {
|
||||
if reg.ip != initial_response.ip
|
||||
|| reg.prefix_len != initial_response.prefix_len
|
||||
|| reg.gateway != initial_response.gateway
|
||||
{
|
||||
log::error!("虚拟网络发生变化");
|
||||
break;
|
||||
}
|
||||
// 保存服务器版本
|
||||
if !reg.server_version.is_empty() {
|
||||
data_handler.set_server_version(reg.server_version.clone());
|
||||
}
|
||||
}
|
||||
ResponseMessage::Error(e) => {
|
||||
log::error!("注册失败 {e:?}");
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
log::error!("错误的注册消息");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("已连接服务器:{}", self.config.server_addr);
|
||||
data_handler.handle_connected();
|
||||
|
||||
if let Err(e) = self
|
||||
.data_handle_loop(&mut receiver, &data_handler)
|
||||
.await
|
||||
{
|
||||
log::error!("Error on data_handle_loop: {:?}", e);
|
||||
}
|
||||
already_connected = false;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
self.disconnect();
|
||||
data_handler.handle_disconnected();
|
||||
task_group_.stop();
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn data_handle_loop(
|
||||
&mut self,
|
||||
receiver: &mut Receiver<(Bytes, Instant)>,
|
||||
data_handler: &ServerTurnInboundHandler,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut time = crate::utils::time::now_ts_ms();
|
||||
let mut ping_interval = tokio::time::interval(Duration::from_secs(5));
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some((buf,expired)) = receiver.recv() => {
|
||||
if expired < Instant::now(){
|
||||
continue;
|
||||
}
|
||||
self.transport_client.send(buf).await?;
|
||||
}
|
||||
rs = self.transport_client.next() => {
|
||||
time = crate::utils::time::now_ts_ms();
|
||||
let data = rs?;
|
||||
if let Err(e) = data_handler.handle(&mut self.transport_client,data, &self.notifier,time).await{
|
||||
log::warn!("Error handling data: {:?}", e);
|
||||
}
|
||||
}
|
||||
_ = ping_interval.tick() => {
|
||||
let now = crate::utils::time::now_ts_ms();
|
||||
if now > time + Duration::from_secs(20).as_millis() as i64 {
|
||||
bail!("timeout")
|
||||
}
|
||||
data_handler.handle_ping(&mut self.transport_client,now).await?;
|
||||
}
|
||||
else => {
|
||||
bail!("receiver closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coordinated multi-server pre-registration.
|
||||
/// 1. First server uses PRE_REGISTER mode to get IP
|
||||
/// 2. Other servers pre-register with the obtained IP
|
||||
/// 3. Send confirmation to all servers
|
||||
/// 4. Return the registration response
|
||||
pub async fn coordinated_registration(
|
||||
managers: &mut Vec<ServerTurnManager>,
|
||||
) -> anyhow::Result<RegResponseMsg> {
|
||||
if managers.is_empty() {
|
||||
bail!("No servers to register");
|
||||
}
|
||||
|
||||
// Step 1: First server pre-register to get IP
|
||||
log::info!(
|
||||
"Starting coordinated registration with {} servers",
|
||||
managers.len()
|
||||
);
|
||||
let first_response = managers[0]
|
||||
.connect_and_reg(RegistrationMode::PreRegister)
|
||||
.await?;
|
||||
|
||||
let ip = match &first_response {
|
||||
ResponseMessage::Reg(reg) => reg.ip,
|
||||
ResponseMessage::Error(e) => bail!("First server registration failed: {}", e.message),
|
||||
_ => bail!("Unexpected response from first server"),
|
||||
};
|
||||
log::info!("Got IP {} from first server", ip);
|
||||
|
||||
// Step 2: Set IP and pre-register with other servers
|
||||
for manager in managers.iter_mut().skip(1) {
|
||||
manager.set_ip(ip);
|
||||
}
|
||||
|
||||
if managers.len() > 1 {
|
||||
let other_results: Vec<_> = futures::future::join_all(
|
||||
managers
|
||||
.iter_mut()
|
||||
.skip(1)
|
||||
.map(|m| m.connect_and_reg(RegistrationMode::PreRegister)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Check all responses
|
||||
for (i, result) in other_results.iter().enumerate() {
|
||||
match result {
|
||||
Ok(ResponseMessage::Reg(_)) => {
|
||||
log::info!("Server {} pre-registered successfully", i + 1);
|
||||
}
|
||||
Ok(ResponseMessage::Error(e)) => {
|
||||
bail!("Server {} registration failed: {}", i + 1, e.message)
|
||||
}
|
||||
Err(e) => bail!("Server {} registration failed: {}", i + 1, e),
|
||||
_ => bail!("Unexpected response from server {}", i + 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Send confirmation to all servers
|
||||
log::info!("Sending confirmation to all servers");
|
||||
let confirm_results: Vec<_> =
|
||||
futures::future::join_all(managers.iter_mut().map(|m| m.send_confirm())).await;
|
||||
|
||||
// Check all confirmation responses
|
||||
for (i, result) in confirm_results.into_iter().enumerate() {
|
||||
match result {
|
||||
Ok(msg) if msg.success => {
|
||||
log::info!("Server {} confirmed successfully", i);
|
||||
}
|
||||
Ok(_) => bail!("Server {} confirmation failed", i),
|
||||
Err(e) => bail!("Server {} confirmation failed: {}", i, e),
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Coordinated registration completed successfully");
|
||||
// Return first server's response (contains IP info)
|
||||
match first_response {
|
||||
ResponseMessage::Reg(reg) => Ok(reg),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
use crate::compression::PacketCompression;
|
||||
use crate::context::nat::{MyNatInfo, PunchBackoff};
|
||||
use crate::context::{NetworkAddr, NetworkRoute, PeerInfoMap, ServerInfoCollection};
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::enhanced_tunnel::inbound::EnhancedInbound;
|
||||
use crate::fec::FecDecoder;
|
||||
use crate::protocol::client_message::PunchInfo;
|
||||
use crate::protocol::control_message::ClientSimpleInfoList;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::rpc_message::RpcMessageResponse;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::p2p::transport::punch::NatPuncher;
|
||||
use crate::tunnel_core::server::rpc::RpcNotifier;
|
||||
use crate::tunnel_core::server::transport::TransportClient;
|
||||
use anyhow::bail;
|
||||
use pnet_packet::Packet;
|
||||
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
|
||||
use pnet_packet::ipv4::Ipv4Packet;
|
||||
use prost::Message;
|
||||
use rust_p2p_core::nat::NatInfo;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
pub(crate) struct ServerTurnInboundHandler {
|
||||
server_id: u32,
|
||||
network_addr: Option<NetworkAddr>,
|
||||
network_route: NetworkRoute,
|
||||
server_info: ServerInfoCollection,
|
||||
nat_info: MyNatInfo,
|
||||
peer_map: PeerInfoMap,
|
||||
punch_backoff: PunchBackoff,
|
||||
puncher: NatPuncher,
|
||||
packet_crypto: PacketCrypto,
|
||||
packet_compression: PacketCompression,
|
||||
enhanced_inbound: EnhancedInbound,
|
||||
fec_decoder: FecDecoder,
|
||||
}
|
||||
impl ServerTurnInboundHandler {
|
||||
pub fn new(
|
||||
server_id: u32,
|
||||
network_addr: NetworkAddr,
|
||||
config: Box<super::connection_manager::InboundHandlerConfig>,
|
||||
) -> Self {
|
||||
let config = *config;
|
||||
Self {
|
||||
server_id,
|
||||
network_addr: Some(network_addr),
|
||||
network_route: config.network_route,
|
||||
server_info: config.server_info,
|
||||
nat_info: config.nat_info,
|
||||
peer_map: config.peer_map,
|
||||
punch_backoff: config.punch_backoff,
|
||||
puncher: config.puncher,
|
||||
packet_crypto: config.packet_crypto,
|
||||
packet_compression: config.packet_compression,
|
||||
enhanced_inbound: config.enhanced_inbound,
|
||||
fec_decoder: config.fec_decoder,
|
||||
}
|
||||
}
|
||||
fn network_contains(&self, ip: &Ipv4Addr) -> bool {
|
||||
self.network_route.network_contains(ip)
|
||||
}
|
||||
fn filter_ip(&self, mut info: NatInfo) -> NatInfo {
|
||||
if self.network_contains(&info.local_ipv4) {
|
||||
info.local_ipv4 = Ipv4Addr::UNSPECIFIED;
|
||||
}
|
||||
info.local_ipv4s.retain(|ip| !self.network_contains(ip));
|
||||
info
|
||||
}
|
||||
fn get_punch_info(&self) -> Option<PunchInfo> {
|
||||
self.nat_info.get().map(|info| PunchInfo {
|
||||
nat_info: self.filter_ip(info),
|
||||
})
|
||||
}
|
||||
fn update_peer_nat_info(&self, ip: Ipv4Addr, nat_info: NatInfo) {
|
||||
self.peer_map.update_nat_info(ip, nat_info);
|
||||
}
|
||||
|
||||
pub async fn handle_server_data(
|
||||
& self,
|
||||
transport_client: &mut TransportClient,
|
||||
network_addr: NetworkAddr,
|
||||
data: TransmissionBytes,
|
||||
rpc_notifier: &RpcNotifier,
|
||||
now: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
let net_packet = NetPacket::new(data)?;
|
||||
let src = net_packet.src_id().into();
|
||||
let msg_type = net_packet.msg_type()?;
|
||||
let mut net_packet = self.packet_compression.decompress(net_packet)?;
|
||||
|
||||
match msg_type {
|
||||
MsgType::Turn => {
|
||||
// 只允许icmp EchoReply
|
||||
let Some(ipv4) = Ipv4Packet::new(net_packet.payload()) else {
|
||||
return Ok(());
|
||||
};
|
||||
if ipv4.get_version() != 4 {
|
||||
return Ok(());
|
||||
}
|
||||
if ipv4.get_next_level_protocol() != pnet_packet::ip::IpNextHeaderProtocols::Icmp {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(icmp) = IcmpPacket::new(ipv4.payload()) else {
|
||||
return Ok(());
|
||||
};
|
||||
if icmp.get_icmp_type() != IcmpTypes::EchoReply {
|
||||
return Ok(());
|
||||
}
|
||||
self.enhanced_inbound
|
||||
.inbound(&network_addr, msg_type, src, net_packet)
|
||||
.await?;
|
||||
}
|
||||
MsgType::Ping => {
|
||||
net_packet.set_ttl(2);
|
||||
net_packet.set_msg_type(MsgType::Pong);
|
||||
net_packet.set_src_id(network_addr.ip.into());
|
||||
net_packet.set_dest_id(src.into());
|
||||
transport_client.send_turn(net_packet).await?;
|
||||
}
|
||||
MsgType::PongTurn => {
|
||||
// 服务端ping 回复,记录延迟
|
||||
if net_packet.payload().len() == 8 + 8 {
|
||||
let time = i64::from_be_bytes(net_packet.payload()[..8].try_into()?);
|
||||
// let data_version = u64::from_be_bytes(net_packet.payload()[8..].try_into()?);
|
||||
if now >= time {
|
||||
self.server_info
|
||||
.set_server_rtt(self.server_id, (now - time) as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
MsgType::PushClientIps => {
|
||||
let list = ClientSimpleInfoList::from_slice(net_packet.payload())?;
|
||||
self.server_info.update_client_simple_list(
|
||||
self.server_id,
|
||||
network_addr.ip,
|
||||
list,
|
||||
now,
|
||||
);
|
||||
}
|
||||
MsgType::RpcRes => {
|
||||
// 设置rpc响应
|
||||
let response = RpcMessageResponse::decode(net_packet.payload())?;
|
||||
rpc_notifier.notify_response(response);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn handle_client_data(
|
||||
&self,
|
||||
network_addr: NetworkAddr,
|
||||
transport_client: &mut TransportClient,
|
||||
data: TransmissionBytes,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut net_packet = NetPacket::new(data)?;
|
||||
let msg_type = net_packet.msg_type()?;
|
||||
let src = Ipv4Addr::from(net_packet.src_id());
|
||||
let dest = Ipv4Addr::from(net_packet.dest_id());
|
||||
|
||||
if msg_type == MsgType::Quic {
|
||||
// QUIC 数据不加密不压缩,但可能有 FEC
|
||||
if net_packet.is_fec() {
|
||||
let packets = self.fec_decoder.receive(net_packet)?;
|
||||
if let Some(packets) = packets {
|
||||
for pkt in packets {
|
||||
self.enhanced_inbound
|
||||
.inbound(&network_addr, msg_type, src, pkt)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
self.enhanced_inbound
|
||||
.inbound(&network_addr, msg_type, src, net_packet)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 解密
|
||||
if let Err(e) = self.packet_crypto.decrypt_in_place(&mut net_packet) {
|
||||
log::error!("{},mst_type={msg_type:?},src={src},dst={dest}", e);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// FEC 解码(始终尝试解码,如果有 FEC 标志)
|
||||
if net_packet.is_fec() {
|
||||
let packets = self.fec_decoder.receive(net_packet)?;
|
||||
if let Some(packets) = packets {
|
||||
for pkt in packets {
|
||||
let pkt = self.packet_compression.decompress(pkt)?;
|
||||
self.process_decompressed_packet(
|
||||
network_addr,
|
||||
transport_client,
|
||||
pkt,
|
||||
msg_type,
|
||||
src,
|
||||
dest,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 解压缩
|
||||
let net_packet = self.packet_compression.decompress(net_packet)?;
|
||||
self.process_decompressed_packet(
|
||||
network_addr,
|
||||
transport_client,
|
||||
net_packet,
|
||||
msg_type,
|
||||
src,
|
||||
dest,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn process_decompressed_packet(
|
||||
&self,
|
||||
network_addr: NetworkAddr,
|
||||
transport_client: &mut TransportClient,
|
||||
net_packet: NetPacket<TransmissionBytes>,
|
||||
msg_type: MsgType,
|
||||
src: Ipv4Addr,
|
||||
dest: Ipv4Addr,
|
||||
) -> anyhow::Result<()> {
|
||||
match msg_type {
|
||||
MsgType::Turn | MsgType::Broadcast => {
|
||||
self.enhanced_inbound
|
||||
.inbound(&network_addr, msg_type, src, net_packet)
|
||||
.await?;
|
||||
}
|
||||
MsgType::PunchStart1 => {
|
||||
// 对方发起打洞
|
||||
let peer_punch_info = PunchInfo::from_slice(net_packet.payload())?;
|
||||
let Some(self_punch_info) = self.get_punch_info() else {
|
||||
return Ok(());
|
||||
};
|
||||
log::info!(
|
||||
"对方主动发起打洞 对方nat信息={peer_punch_info:?},自己nat信息={self_punch_info:?} {src}->{dest}"
|
||||
);
|
||||
self.update_peer_nat_info(src, peer_punch_info.nat_info.clone());
|
||||
let rs = self.puncher.punch(src, peer_punch_info)?;
|
||||
if rs {
|
||||
let bytes_mut = self_punch_info.encode();
|
||||
let mut net_packet = NetPacket::new(TransmissionBytes::zeroed_size(
|
||||
HEAD_LENGTH + bytes_mut.len(),
|
||||
self.packet_crypto.encrypt_reserve(),
|
||||
))?;
|
||||
net_packet.set_msg_type(MsgType::PunchStart2);
|
||||
net_packet.set_ttl(2);
|
||||
net_packet.set_src_id(dest.into());
|
||||
net_packet.set_dest_id(src.into());
|
||||
net_packet.set_payload(&bytes_mut)?;
|
||||
self.packet_crypto.encrypt_in_place(&mut net_packet)?;
|
||||
transport_client.send_turn(net_packet).await?;
|
||||
}else{
|
||||
log::info!("限制打洞频率")
|
||||
}
|
||||
}
|
||||
MsgType::PunchStart2 => {
|
||||
self.punch_backoff.record(src);
|
||||
// 对方回复开始打洞
|
||||
let peer_punch_info = PunchInfo::from_slice(net_packet.payload())?;
|
||||
self.update_peer_nat_info(src, peer_punch_info.nat_info.clone());
|
||||
log::info!("对方回复开始打洞 {:?} {src}->{dest}", peer_punch_info);
|
||||
self.puncher.punch_uncheck(src, peer_punch_info)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle(
|
||||
&self,
|
||||
transport_client: &mut TransportClient,
|
||||
data: TransmissionBytes,
|
||||
rpc_notifier: &RpcNotifier,
|
||||
now: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
let net_packet = NetPacket::new(&data)?;
|
||||
let Some(network_addr) = self.network_addr else {
|
||||
bail!("未找到自身IP")
|
||||
};
|
||||
|
||||
if net_packet.is_gateway() {
|
||||
// 服务端数据
|
||||
return self
|
||||
.handle_server_data(transport_client, network_addr, data, rpc_notifier, now)
|
||||
.await;
|
||||
}
|
||||
let dest = Ipv4Addr::from(net_packet.dest_id());
|
||||
if !dest.is_broadcast() && !dest.is_unspecified() && network_addr.ip != dest {
|
||||
return Ok(());
|
||||
}
|
||||
self.handle_client_data(network_addr, transport_client, data)
|
||||
.await
|
||||
}
|
||||
pub async fn handle_ping(
|
||||
&self,
|
||||
transport_client: &mut TransportClient,
|
||||
now: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut ping_packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + 8 + 8))?;
|
||||
ping_packet.set_ttl(1);
|
||||
ping_packet.set_msg_type(MsgType::PingTurn);
|
||||
ping_packet.set_gateway_flag(true);
|
||||
ping_packet.set_payload(&now.to_be_bytes())?;
|
||||
ping_packet.payload_mut()[0..8].copy_from_slice(&now.to_be_bytes());
|
||||
ping_packet.payload_mut()[8..]
|
||||
.copy_from_slice(&self.server_info.data_version(self.server_id).to_be_bytes());
|
||||
transport_client
|
||||
.send(ping_packet.into_buffer().into_bytes().freeze())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn handle_connected(&self) {
|
||||
self.server_info.set_server_connected(self.server_id, true);
|
||||
self.server_info
|
||||
.set_last_connected_time(self.server_id, Some(crate::utils::time::now_ts_ms()));
|
||||
self.server_info.set_disconnected_time(self.server_id, None);
|
||||
}
|
||||
pub fn set_server_version(&self, version: String) {
|
||||
self.server_info.set_server_version(self.server_id, version);
|
||||
}
|
||||
pub fn handle_disconnected(&self) {
|
||||
if self.server_info.set_server_connected(self.server_id, false) {
|
||||
self.server_info
|
||||
.set_disconnected_time(self.server_id, Some(crate::utils::time::now_ts_ms()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub(crate) mod connection_manager;
|
||||
pub(crate) mod inbound;
|
||||
pub(crate) mod outbound;
|
||||
pub(crate) mod rpc;
|
||||
pub mod transport;
|
||||
@@ -0,0 +1,329 @@
|
||||
use crate::context::ServerInfoCollection;
|
||||
use crate::crypto::PacketCrypto;
|
||||
use crate::protocol::ProtoToBytesMut;
|
||||
use crate::protocol::control_message::SelectiveBroadcast;
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ServerOutbound {
|
||||
server_id_list: Arc<Vec<u32>>,
|
||||
sender: Arc<HashMap<u32, Sender<(Bytes, Instant)>>>,
|
||||
server_info_collection: ServerInfoCollection,
|
||||
packet_crypto: PacketCrypto,
|
||||
}
|
||||
impl ServerOutbound {
|
||||
pub fn new(
|
||||
sender: Arc<HashMap<u32, Sender<(Bytes, Instant)>>>,
|
||||
server_info_collection: ServerInfoCollection,
|
||||
packet_crypto: PacketCrypto,
|
||||
) -> Self {
|
||||
let server_id_list = Arc::new(sender.keys().copied().collect());
|
||||
Self {
|
||||
server_id_list,
|
||||
sender,
|
||||
server_info_collection,
|
||||
packet_crypto,
|
||||
}
|
||||
}
|
||||
pub fn exists_route(&self, dest: &Ipv4Addr) -> bool {
|
||||
self.server_info_collection.exists_online_client_ip(dest)
|
||||
}
|
||||
pub fn server_id_list(&self) -> &Vec<u32> {
|
||||
&self.server_id_list
|
||||
}
|
||||
pub fn encrypt_reserve(&self) -> usize {
|
||||
self.packet_crypto.encrypt_reserve()
|
||||
}
|
||||
|
||||
pub async fn send_to_gateway_expired(
|
||||
&self,
|
||||
server_id: u32,
|
||||
mut buf: NetPacket<TransmissionBytes>,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.server_info_collection.is_server_connected(server_id) {
|
||||
bail!("未连接服务器")
|
||||
}
|
||||
buf.set_gateway_flag(true);
|
||||
self.send_expired_impl(server_id, buf, expired).await
|
||||
}
|
||||
|
||||
pub async fn send(
|
||||
&self,
|
||||
dest_ip: Ipv4Addr,
|
||||
buf: NetPacket<TransmissionBytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.send_expired(dest_ip, buf, Duration::from_secs(5))
|
||||
.await
|
||||
}
|
||||
pub async fn send_expired(
|
||||
&self,
|
||||
dest_ip: Ipv4Addr,
|
||||
buf: NetPacket<TransmissionBytes>,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(server_id) = self
|
||||
.server_info_collection
|
||||
.find_ip_to_server(&self.server_id_list, &dest_ip)
|
||||
else {
|
||||
bail!("not found ip route: {dest_ip}")
|
||||
};
|
||||
self.send_expired_impl(server_id, buf, expired).await
|
||||
}
|
||||
async fn send_expired_impl(
|
||||
&self,
|
||||
server_id: u32,
|
||||
mut buf: NetPacket<TransmissionBytes>,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
if !buf.is_gateway() {
|
||||
self.packet_crypto.encrypt_in_place(&mut buf)?;
|
||||
}
|
||||
let Some(sender) = self.sender.get(&server_id) else {
|
||||
bail!("not found server")
|
||||
};
|
||||
sender
|
||||
.send_timeout(
|
||||
(
|
||||
buf.into_buffer().into_bytes().freeze(),
|
||||
Instant::now() + expired,
|
||||
),
|
||||
expired,
|
||||
)
|
||||
.await
|
||||
.context("connect server task failed")
|
||||
}
|
||||
|
||||
pub async fn send_raw(&self, dest_ip: Ipv4Addr, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
|
||||
let Some(server_id) = self
|
||||
.server_info_collection
|
||||
.find_ip_to_server(&self.server_id_list, &dest_ip)
|
||||
else {
|
||||
bail!("not found ip route: {dest_ip}")
|
||||
};
|
||||
let expired = Duration::from_secs(5);
|
||||
let Some(sender) = self.sender.get(&server_id) else {
|
||||
bail!("not found server")
|
||||
};
|
||||
sender
|
||||
.send_timeout((buf.into_buffer(), Instant::now() + expired), expired)
|
||||
.await
|
||||
.context("connect server task failed")
|
||||
}
|
||||
pub async fn send_default_raw(&self, buf: NetPacket<Bytes>) -> anyhow::Result<()> {
|
||||
let Some(server_id) = self
|
||||
.server_info_collection
|
||||
.find_connected_server(&self.server_id_list)
|
||||
else {
|
||||
bail!("not found default route")
|
||||
};
|
||||
let expired = Duration::from_secs(5);
|
||||
let Some(sender) = self.sender.get(&server_id) else {
|
||||
bail!("not found server")
|
||||
};
|
||||
sender
|
||||
.send_timeout((buf.into_buffer(), Instant::now() + expired), expired)
|
||||
.await
|
||||
.context("connect server task failed")
|
||||
}
|
||||
|
||||
pub async fn send_raw_broadcast(
|
||||
&self,
|
||||
exclude_ips: Option<Vec<Ipv4Addr>>,
|
||||
buf: NetPacket<Bytes>,
|
||||
) -> anyhow::Result<()> {
|
||||
let buf = buf.into_buffer();
|
||||
let expired = Duration::from_secs(5);
|
||||
|
||||
let map: HashMap<u32, (Vec<Ipv4Addr>, u32)> =
|
||||
self.server_info_collection.server_client_ip_map();
|
||||
if map.is_empty() {
|
||||
bail!("no connected servers with clients");
|
||||
}
|
||||
|
||||
// 只有一个服务器,直接发送
|
||||
if map.len() == 1 {
|
||||
let (server_id, (ips, _)) = map.iter().next().expect("map has exactly one element");
|
||||
if ips.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let sender = self
|
||||
.sender
|
||||
.get(server_id)
|
||||
.context("server sender not found")?;
|
||||
|
||||
return if let Some(exclude_ips) = exclude_ips {
|
||||
send_exclude_broadcast(sender.clone(), buf, exclude_ips, expired).await
|
||||
} else {
|
||||
send_direct(sender.clone(), buf, expired).await
|
||||
};
|
||||
}
|
||||
|
||||
// 找到最优服务器
|
||||
let (max_server_id, (max_ips, _)) = map
|
||||
.iter()
|
||||
.max_by(|(_, (ips_a, rtt_a)), (_, (ips_b, rtt_b))| {
|
||||
let score_a = ips_a.len() as f64 / (*rtt_a as f64 + 1.0);
|
||||
let score_b = ips_b.len() as f64 / (*rtt_b as f64 + 1.0);
|
||||
score_a
|
||||
.partial_cmp(&score_b)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.context("failed to find server with most IPs")?;
|
||||
if max_ips.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let max_ip_set: std::collections::HashSet<_> = max_ips.iter().collect();
|
||||
let exclude_set: std::collections::HashSet<_> = exclude_ips
|
||||
.as_ref()
|
||||
.map(|ips| ips.iter().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
// 任务1: 向主服务器发送
|
||||
let sender = self
|
||||
.sender
|
||||
.get(max_server_id)
|
||||
.cloned()
|
||||
.context("max server sender not found")?;
|
||||
|
||||
if let Some(exclude_ips) = exclude_ips.clone() {
|
||||
let buf_clone = buf.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
send_exclude_broadcast(sender, buf_clone, exclude_ips, expired).await
|
||||
});
|
||||
handles.push(handle);
|
||||
} else {
|
||||
let buf_clone = buf.clone();
|
||||
let handle = tokio::spawn(async move { send_direct(sender, buf_clone, expired).await });
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// 任务2-N: 向其他服务器发送目标广播
|
||||
for (server_id, (ips, _rtt)) in map.iter() {
|
||||
if *server_id == *max_server_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 筛选目标IP:不在最大服务器中,也不在排除列表中
|
||||
let target_ips: Vec<Ipv4Addr> = ips
|
||||
.iter()
|
||||
.filter(|ip| !max_ip_set.contains(ip) && !exclude_set.contains(ip))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
if target_ips.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sender = match self.sender.get(server_id).cloned() {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let buf_clone = buf.clone();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
send_target_broadcast(sender, target_ips, buf_clone, expired).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
let mut errors = Vec::new();
|
||||
for handle in handles {
|
||||
match handle.await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => errors.push(anyhow::anyhow!("task join error: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
bail!(
|
||||
"broadcast failed with {} errors: {:?}",
|
||||
errors.len(),
|
||||
errors
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn send_exclude_broadcast(
|
||||
sender: Sender<(Bytes, Instant)>,
|
||||
buf: Bytes,
|
||||
exclude_ips: Vec<Ipv4Addr>,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
let broadcast = SelectiveBroadcast::new(&exclude_ips, buf.to_vec());
|
||||
let bytes = broadcast.encode_bytes_mut();
|
||||
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + bytes.len()))?;
|
||||
packet.set_msg_type(MsgType::ExcludeBroadcast);
|
||||
packet.set_ttl(5);
|
||||
packet.payload_mut().copy_from_slice(&bytes);
|
||||
|
||||
sender
|
||||
.send_timeout(
|
||||
(
|
||||
packet.into_buffer().into_bytes().freeze(),
|
||||
Instant::now() + expired,
|
||||
),
|
||||
expired,
|
||||
)
|
||||
.await
|
||||
.context("failed to send exclude broadcast")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 直接发送原始数据
|
||||
async fn send_direct(
|
||||
sender: Sender<(Bytes, Instant)>,
|
||||
buf: Bytes,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
sender
|
||||
.send_timeout((buf, Instant::now() + expired), expired)
|
||||
.await
|
||||
.context("failed to send direct broadcast")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_target_broadcast(
|
||||
sender: Sender<(Bytes, Instant)>,
|
||||
target_ips: Vec<Ipv4Addr>,
|
||||
buf: Bytes,
|
||||
expired: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
let target_broadcast = SelectiveBroadcast::new(&target_ips, buf.to_vec());
|
||||
let target_bytes = target_broadcast.encode_bytes_mut();
|
||||
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + target_bytes.len()))?;
|
||||
packet.set_msg_type(MsgType::TargetBroadcast);
|
||||
packet.set_ttl(5);
|
||||
packet.payload_mut().copy_from_slice(&target_bytes);
|
||||
|
||||
sender
|
||||
.send_timeout(
|
||||
(
|
||||
packet.into_buffer().into_bytes().freeze(),
|
||||
Instant::now() + expired,
|
||||
),
|
||||
expired,
|
||||
)
|
||||
.await
|
||||
.context("failed to send target broadcast")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, MsgType, NetPacket};
|
||||
use crate::protocol::rpc_message::rpc_message_request::RpcReqPayload;
|
||||
use crate::protocol::rpc_message::rpc_message_response::RpcResPayload;
|
||||
use crate::protocol::rpc_message::{
|
||||
ClientInfo, ClientListRequest, ClientListResponse, RpcMessageRequest, RpcMessageResponse,
|
||||
};
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::server::outbound::ServerOutbound;
|
||||
use anyhow::bail;
|
||||
use parking_lot::Mutex;
|
||||
use prost::Message;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerRPC {
|
||||
tunnel_to_server: ServerOutbound,
|
||||
rpc_notifier: HashMap<u32, RpcNotifier>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RpcNotifier {
|
||||
pending_requests: Arc<Mutex<HashMap<u64, Sender<RpcMessageResponse>>>>,
|
||||
rpc_id: Arc<Mutex<u64>>,
|
||||
}
|
||||
|
||||
impl RpcNotifier {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pending_requests: Arc::new(Mutex::new(HashMap::new())),
|
||||
rpc_id: Arc::new(Mutex::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_request_and_waiter(&self) -> RpcResponseWaiter {
|
||||
let id: u64 = {
|
||||
let mut id_lock = self.rpc_id.lock();
|
||||
*id_lock += 1;
|
||||
*id_lock
|
||||
};
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
{
|
||||
let mut pending = self.pending_requests.lock();
|
||||
pending.insert(id, tx);
|
||||
}
|
||||
|
||||
RpcResponseWaiter {
|
||||
id,
|
||||
pending_requests_handle: Arc::clone(&self.pending_requests),
|
||||
rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_response(&self, response: RpcMessageResponse) {
|
||||
let mut pending = self.pending_requests.lock();
|
||||
|
||||
if let Some(tx) = pending.remove(&response.id) {
|
||||
let _ = tx.send(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RpcResponseWaiter {
|
||||
id: u64,
|
||||
pending_requests_handle: Arc<Mutex<HashMap<u64, Sender<RpcMessageResponse>>>>,
|
||||
rx: oneshot::Receiver<RpcMessageResponse>,
|
||||
}
|
||||
|
||||
impl RpcResponseWaiter {
|
||||
pub async fn wait_for_response(
|
||||
mut self,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<RpcMessageResponse> {
|
||||
let result = tokio::time::timeout(timeout, &mut self.rx).await;
|
||||
|
||||
match result {
|
||||
Err(_) => bail!("timeout waiting for response"),
|
||||
Ok(Ok(response)) => Ok(response),
|
||||
Ok(Err(_)) => bail!("closed connection"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RpcResponseWaiter {
|
||||
fn drop(&mut self) {
|
||||
let mut pending = self.pending_requests_handle.lock();
|
||||
let _ = pending.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerRPC {
|
||||
pub(crate) fn new(
|
||||
tunnel_to_server: ServerOutbound,
|
||||
rpc_notifier: HashMap<u32, RpcNotifier>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tunnel_to_server,
|
||||
rpc_notifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn client_list(&self) -> anyhow::Result<ClientListResponse> {
|
||||
let mut map: HashMap<String, ClientInfo> = HashMap::new();
|
||||
for server_id in self.tunnel_to_server.server_id_list() {
|
||||
match self.client_list_target(*server_id).await {
|
||||
Ok(rs) => {
|
||||
for client in rs.list {
|
||||
map.entry(client.id.clone()).or_insert(client);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("client list target failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ClientListResponse {
|
||||
list: map.into_values().collect(),
|
||||
})
|
||||
}
|
||||
pub async fn client_list_target(&self, server_id: u32) -> anyhow::Result<ClientListResponse> {
|
||||
let Some(rpc_notifier) = self.rpc_notifier.get(&server_id) else {
|
||||
bail!("no RPC notifier");
|
||||
};
|
||||
let waiter = rpc_notifier.create_request_and_waiter();
|
||||
let request = RpcMessageRequest {
|
||||
id: waiter.id,
|
||||
rpc_req_payload: Some(RpcReqPayload::ClientListReq(ClientListRequest::default())),
|
||||
};
|
||||
let buf = request.encode_to_vec();
|
||||
let mut packet = NetPacket::new(TransmissionBytes::zeroed(HEAD_LENGTH + buf.len()))?;
|
||||
packet.set_msg_type(MsgType::RpcReq);
|
||||
packet.set_gateway_flag(true);
|
||||
packet.set_ttl(1);
|
||||
packet.set_payload(&buf)?;
|
||||
|
||||
self.tunnel_to_server
|
||||
.send_to_gateway_expired(server_id, packet, Duration::from_secs(1))
|
||||
.await?;
|
||||
let response = waiter.wait_for_response(Duration::from_secs(3)).await?;
|
||||
if let Some(RpcResPayload::ClientListRes(res)) = response.rpc_res_payload {
|
||||
return Ok(res);
|
||||
}
|
||||
bail!("unexpected response: {:?}", response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
use crate::protocol::control_message::{RegRequestMsg, RegistrationMode};
|
||||
use crate::tls::verifier::CertValidationMode;
|
||||
use anyhow::Context;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConnectRegConfig {
|
||||
pub server_addr: ProtocolAddress,
|
||||
pub cert_mode: CertValidationMode,
|
||||
pub network_code: String,
|
||||
pub device_id: String,
|
||||
pub device_name: String,
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
pub key_sign: Option<String>,
|
||||
pub ip_variable: bool,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConnectConfig {
|
||||
pub protocol_type: ProtocolType,
|
||||
pub server_addr: SocketAddr,
|
||||
pub server_domain: String,
|
||||
pub cert_mode: CertValidationMode,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub enum ProtocolType {
|
||||
Quic,
|
||||
#[default]
|
||||
TlsTcp,
|
||||
Wss,
|
||||
Dynamic,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProtocolAddress {
|
||||
pub protocol_type: ProtocolType,
|
||||
pub address: String,
|
||||
}
|
||||
impl Default for ProtocolAddress {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
protocol_type: ProtocolType::default(),
|
||||
address: "127.0.0.1:29872".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for ProtocolAddress {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (protocol_type, server_addr) = parse_server(s)?;
|
||||
Ok(Self {
|
||||
protocol_type,
|
||||
address: server_addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ProtocolAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let prefix = match self.protocol_type {
|
||||
ProtocolType::Quic => "quic://",
|
||||
ProtocolType::TlsTcp => "tcp://",
|
||||
ProtocolType::Wss => "wss://",
|
||||
ProtocolType::Dynamic => "dynamic://",
|
||||
};
|
||||
write!(f, "{}{}", prefix, self.address)
|
||||
}
|
||||
}
|
||||
pub fn parse_server(val: &str) -> Result<(ProtocolType, String), String> {
|
||||
let val = val.trim().to_lowercase();
|
||||
if let Some(s) = val.strip_prefix("quic://") {
|
||||
return Ok((ProtocolType::Quic, s.to_string()));
|
||||
}
|
||||
if let Some(s) = val.strip_prefix("tcp://") {
|
||||
return Ok((ProtocolType::TlsTcp, s.to_string()));
|
||||
}
|
||||
if let Some(s) = val.strip_prefix("wss://") {
|
||||
return Ok((ProtocolType::Wss, s.to_string()));
|
||||
}
|
||||
if let Some(s) = val.strip_prefix("dynamic://") {
|
||||
return Ok((ProtocolType::Dynamic, s.to_string()));
|
||||
}
|
||||
if val.contains("://") {
|
||||
return Err(format!("Unknown protocol in server address: {}", val));
|
||||
}
|
||||
Ok((ProtocolType::TlsTcp, val))
|
||||
}
|
||||
impl ConnectRegConfig {
|
||||
pub fn reg_msg_request(
|
||||
&self,
|
||||
server_id: u32,
|
||||
registration_mode: RegistrationMode,
|
||||
) -> RegRequestMsg {
|
||||
RegRequestMsg {
|
||||
network_code: self.network_code.to_string(),
|
||||
device_id: self.device_id.to_string(),
|
||||
ip: self.ip,
|
||||
name: self.device_name.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
key_sign: self.key_sign.clone(),
|
||||
ip_variable: self.ip_variable,
|
||||
server_id,
|
||||
registration_mode,
|
||||
}
|
||||
}
|
||||
pub async fn to_connect_config(&self) -> anyhow::Result<ConnectConfig> {
|
||||
let (protocol_type, server_domain) = match self.server_addr.protocol_type {
|
||||
ProtocolType::Dynamic => {
|
||||
let mut txt = crate::utils::dns_query::dns_query_txt(
|
||||
&self.server_addr.address,
|
||||
vec![],
|
||||
&None,
|
||||
)
|
||||
.await?;
|
||||
txt.shuffle(&mut rand::rng());
|
||||
let x = txt.first().context("DNS query failed")?;
|
||||
let x = x.to_lowercase();
|
||||
let (protocol_type, domain) = if let Some(v) = x.strip_prefix("udp://") {
|
||||
(ProtocolType::Quic, v)
|
||||
} else if let Some(v) = x.strip_prefix("quic://") {
|
||||
(ProtocolType::Quic, v)
|
||||
} else if let Some(v) = x.strip_prefix("tcp://") {
|
||||
(ProtocolType::TlsTcp, v)
|
||||
} else if let Some(v) = x.strip_prefix("ws://") {
|
||||
(ProtocolType::TlsTcp, v)
|
||||
} else if let Some(v) = x.strip_prefix("wss://") {
|
||||
(ProtocolType::TlsTcp, v)
|
||||
} else {
|
||||
(ProtocolType::TlsTcp, x.as_str())
|
||||
};
|
||||
(protocol_type, domain.to_owned())
|
||||
}
|
||||
v => (v, self.server_addr.address.to_string()),
|
||||
};
|
||||
let server_addr =
|
||||
crate::utils::dns_query::dns_query_one(&server_domain, &vec![], &None).await?;
|
||||
let server_domain = strip_port(&server_domain).to_owned();
|
||||
Ok(ConnectConfig {
|
||||
protocol_type,
|
||||
server_addr,
|
||||
server_domain,
|
||||
cert_mode: self.cert_mode.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
fn strip_port(addr: &str) -> &str {
|
||||
if let Some(stripped) = addr.strip_prefix('[')
|
||||
&& let Some(pos) = stripped.find(']')
|
||||
{
|
||||
return &stripped[..pos];
|
||||
}
|
||||
|
||||
if addr.contains(':') && !addr.contains('.') && addr.matches(':').count() > 1 {
|
||||
return addr;
|
||||
}
|
||||
|
||||
if let Some((host, port)) = addr.rsplit_once(':')
|
||||
&& port.chars().all(|c| c.is_ascii_digit())
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
addr
|
||||
}
|
||||
impl ConnectConfig {
|
||||
pub fn server_addr(&self) -> SocketAddr {
|
||||
self.server_addr
|
||||
}
|
||||
pub fn server_name(&self) -> &String {
|
||||
&self.server_domain
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use crate::protocol::ip_packet_protocol::NetPacket;
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::server::transport::config::{ConnectConfig, ProtocolType};
|
||||
use crate::tunnel_core::server::transport::quic::QuicTransport;
|
||||
use crate::tunnel_core::server::transport::tcp::TlsTcpTransport;
|
||||
use crate::tunnel_core::server::transport::wss::WssTransport;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod config;
|
||||
pub(crate) mod quic;
|
||||
pub(crate) mod tcp;
|
||||
pub(crate) mod wss;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum TransportClient {
|
||||
Quic(QuicTransport),
|
||||
TlsTcp(TlsTcpTransport),
|
||||
Wss(WssTransport),
|
||||
#[default]
|
||||
Pending,
|
||||
}
|
||||
|
||||
impl TransportClient {
|
||||
pub fn new() -> Self {
|
||||
TransportClient::default()
|
||||
}
|
||||
pub fn disconnect(&mut self) {
|
||||
match self {
|
||||
TransportClient::Quic(c) => c.disconnect(),
|
||||
TransportClient::TlsTcp(c) => c.disconnect(),
|
||||
TransportClient::Wss(c) => c.disconnect(),
|
||||
TransportClient::Pending => {}
|
||||
};
|
||||
*self = TransportClient::Pending;
|
||||
}
|
||||
pub async fn connect_timeout(
|
||||
&mut self,
|
||||
config: &ConnectConfig,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
tokio::time::timeout(timeout, self.connect(config))
|
||||
.await
|
||||
.context("timeout")?
|
||||
}
|
||||
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
|
||||
match self {
|
||||
TransportClient::Quic(c) => c.connect(config).await?,
|
||||
TransportClient::TlsTcp(c) => c.connect(config).await?,
|
||||
TransportClient::Wss(c) => c.connect(config).await?,
|
||||
TransportClient::Pending => match config.protocol_type {
|
||||
ProtocolType::Quic => {
|
||||
let mut transport = QuicTransport::new();
|
||||
transport.connect(config).await?;
|
||||
*self = TransportClient::Quic(transport);
|
||||
}
|
||||
ProtocolType::TlsTcp => {
|
||||
let mut transport = TlsTcpTransport::new();
|
||||
transport.connect(config).await?;
|
||||
*self = TransportClient::TlsTcp(transport);
|
||||
}
|
||||
ProtocolType::Wss => {
|
||||
let mut transport = WssTransport::new();
|
||||
transport.connect(config).await?;
|
||||
*self = TransportClient::Wss(transport);
|
||||
}
|
||||
ProtocolType::Dynamic => {
|
||||
bail!("unreachable connect")
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
|
||||
match self {
|
||||
TransportClient::Quic(c) => c.send(buf).await,
|
||||
TransportClient::TlsTcp(c) => c.send(buf).await,
|
||||
TransportClient::Wss(c) => c.send(buf).await,
|
||||
TransportClient::Pending => {
|
||||
bail!("Not connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
|
||||
match self {
|
||||
TransportClient::Quic(c) => c.next().await,
|
||||
TransportClient::TlsTcp(c) => c.next().await,
|
||||
TransportClient::Wss(c) => c.next().await,
|
||||
|
||||
TransportClient::Pending => {
|
||||
bail!("Not connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn next_timeout(&mut self, timeout: Duration) -> anyhow::Result<TransmissionBytes> {
|
||||
tokio::time::timeout(timeout, self.next())
|
||||
.await
|
||||
.context("timeout")?
|
||||
}
|
||||
pub async fn send_turn(&mut self, buf: NetPacket<TransmissionBytes>) -> anyhow::Result<()> {
|
||||
self.send(buf.into_buffer().into_bytes().freeze()).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tls::verifier::CertValidationMode;
|
||||
use crate::tunnel_core::server::transport::config::ConnectConfig;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use quinn::{ClientConfig, Endpoint, RecvStream, SendStream};
|
||||
use std::sync::Arc;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct QuicTransport {
|
||||
framed: Option<(
|
||||
FramedWrite<SendStream, LengthDelimitedCodec>,
|
||||
FramedRead<RecvStream, LengthDelimitedCodec>,
|
||||
)>,
|
||||
}
|
||||
impl QuicTransport {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn disconnect(&mut self) {
|
||||
self.framed = None;
|
||||
}
|
||||
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
|
||||
if self.framed.is_some() {
|
||||
bail!("Already connected");
|
||||
}
|
||||
let (w, r) = connect_quic(config).await?;
|
||||
self.framed = Some((w, r));
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
|
||||
let Some((w, _r)) = self.framed.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
w.send(buf).await.context("send to server failed")
|
||||
}
|
||||
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
|
||||
let Some((_w, r)) = self.framed.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
r.next()
|
||||
.await
|
||||
.context("EOF")?
|
||||
.context("receive from server failed")
|
||||
.map(TransmissionBytes::from)
|
||||
}
|
||||
}
|
||||
pub async fn connect_quic(
|
||||
config: &ConnectConfig,
|
||||
) -> anyhow::Result<(
|
||||
FramedWrite<SendStream, LengthDelimitedCodec>,
|
||||
FramedRead<RecvStream, LengthDelimitedCodec>,
|
||||
)> {
|
||||
let server_addr = config.server_addr();
|
||||
let server_name = config.server_name();
|
||||
let quic_config = create_client_config(&config.cert_mode)?;
|
||||
let mut endpoint = match Endpoint::client((std::net::Ipv6Addr::UNSPECIFIED, 0).into()) {
|
||||
Ok(endpoint) => endpoint,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to create QUIC endpoint: {}", e);
|
||||
Endpoint::client((std::net::Ipv4Addr::UNSPECIFIED, 0).into())
|
||||
.context("Failed to create QUIC endpoint")?
|
||||
}
|
||||
};
|
||||
|
||||
endpoint.set_default_client_config(quic_config);
|
||||
let connection = endpoint
|
||||
.connect(server_addr, server_name)?
|
||||
.await
|
||||
.context("Failed to establish QUIC connection")?;
|
||||
let (send_stream, recv_stream) = connection
|
||||
.open_bi()
|
||||
.await
|
||||
.context("Failed to open bidirectional stream")?;
|
||||
let framed_write = FramedWrite::new(send_stream, LengthDelimitedCodec::new());
|
||||
let framed_read = FramedRead::new(recv_stream, LengthDelimitedCodec::new());
|
||||
Ok((framed_write, framed_read))
|
||||
}
|
||||
|
||||
fn create_client_config(cert_mode: &CertValidationMode) -> anyhow::Result<ClientConfig> {
|
||||
let config = cert_mode.create_tls_client_config()?;
|
||||
let client_config = ClientConfig::new(Arc::new(
|
||||
quinn::crypto::rustls::QuicClientConfig::try_from(config)
|
||||
.context("Failed to create QUIC client config")?,
|
||||
));
|
||||
|
||||
Ok(client_config)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::server::transport::config::ConnectConfig;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::{TlsConnector, client::TlsStream};
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
|
||||
type TlsTcpStream = TlsStream<TcpStream>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TlsTcpTransport {
|
||||
framed: Option<Framed<TlsTcpStream, LengthDelimitedCodec>>,
|
||||
}
|
||||
|
||||
impl TlsTcpTransport {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn disconnect(&mut self) {
|
||||
self.framed = None;
|
||||
}
|
||||
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
|
||||
if self.framed.is_some() {
|
||||
bail!("Already connected");
|
||||
}
|
||||
let framed = connect_tls_tcp(config).await?;
|
||||
self.framed = Some(framed);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
|
||||
let Some(framed) = self.framed.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
framed.send(buf).await.context("send to server failed")
|
||||
}
|
||||
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
|
||||
let Some(framed) = self.framed.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
framed
|
||||
.next()
|
||||
.await
|
||||
.context("EOF")?
|
||||
.context("receive from server failed")
|
||||
.map(TransmissionBytes::from)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_tls_tcp(
|
||||
config: &ConnectConfig,
|
||||
) -> anyhow::Result<Framed<TlsTcpStream, LengthDelimitedCodec>> {
|
||||
let server_addr = config.server_addr();
|
||||
let server_name = config.server_name().clone();
|
||||
|
||||
let rustls_config = config.cert_mode.create_tls_client_config()?;
|
||||
let connector = TlsConnector::from(Arc::new(rustls_config));
|
||||
|
||||
let tcp_stream = TcpStream::connect(server_addr)
|
||||
.await
|
||||
.context("Failed to establish underlying TCP connection")?;
|
||||
if let Err(e) = tcp_stream.set_nodelay(true) {
|
||||
log::error!("Failed to set TCP_NODELAY: {}", e);
|
||||
}
|
||||
let dns_name = server_name
|
||||
.try_into()
|
||||
.context("Invalid server name for TLS")?;
|
||||
|
||||
let tls_stream = connector
|
||||
.connect(dns_name, tcp_stream)
|
||||
.await
|
||||
.context("Failed to perform TLS handshake")?;
|
||||
|
||||
let framed = Framed::new(tls_stream, LengthDelimitedCodec::new());
|
||||
|
||||
Ok(framed)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use crate::protocol::transmission::TransmissionBytes;
|
||||
use crate::tunnel_core::server::transport::config::ConnectConfig;
|
||||
use anyhow::{Context, bail};
|
||||
use bytes::Bytes;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::{TlsConnector, client::TlsStream};
|
||||
use tokio_tungstenite::{WebSocketStream, client_async, tungstenite::Message};
|
||||
|
||||
type WssStream = WebSocketStream<TlsStream<TcpStream>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WssTransport {
|
||||
stream: Option<WssStream>,
|
||||
}
|
||||
|
||||
impl WssTransport {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn disconnect(&mut self) {
|
||||
self.stream = None;
|
||||
}
|
||||
pub async fn connect(&mut self, config: &ConnectConfig) -> anyhow::Result<()> {
|
||||
if self.stream.is_some() {
|
||||
bail!("Already connected");
|
||||
}
|
||||
let stream = connect_wss(config).await?;
|
||||
self.stream = Some(stream);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send(&mut self, buf: Bytes) -> anyhow::Result<()> {
|
||||
let Some(framed) = self.stream.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
framed
|
||||
.send(Message::Binary(buf))
|
||||
.await
|
||||
.context("send to server failed")
|
||||
}
|
||||
pub async fn next(&mut self) -> anyhow::Result<TransmissionBytes> {
|
||||
let Some(framed) = self.stream.as_mut() else {
|
||||
bail!("Not connected");
|
||||
};
|
||||
loop {
|
||||
let message = framed
|
||||
.next()
|
||||
.await
|
||||
.context("EOF")?
|
||||
.context("receive from server failed")?;
|
||||
match message {
|
||||
Message::Binary(buf) => {
|
||||
return Ok(TransmissionBytes::from(buf));
|
||||
}
|
||||
Message::Close(_) => {
|
||||
bail!("Disconnected");
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_wss(config: &ConnectConfig) -> anyhow::Result<WssStream> {
|
||||
let server_addr = config.server_addr();
|
||||
let server_name = config.server_name().clone();
|
||||
|
||||
let rustls_config = config.cert_mode.create_tls_client_config()?;
|
||||
let connector = TlsConnector::from(Arc::new(rustls_config));
|
||||
|
||||
let tcp_stream = TcpStream::connect(server_addr)
|
||||
.await
|
||||
.context("Failed to establish underlying TCP connection")?;
|
||||
if let Err(e) = tcp_stream.set_nodelay(true) {
|
||||
log::error!("Failed to set TCP_NODELAY: {}", e);
|
||||
}
|
||||
let url = format!("wss://{}", server_name);
|
||||
|
||||
let dns_name = server_name
|
||||
.try_into()
|
||||
.context("Invalid server name for TLS")?;
|
||||
|
||||
let tls_stream = connector
|
||||
.connect(dns_name, tcp_stream)
|
||||
.await
|
||||
.context("Failed to perform TLS handshake")?;
|
||||
|
||||
let (ws_stream, _response) = client_async(url, tls_stream)
|
||||
.await
|
||||
.context("Failed to perform WebSocket handshake")?;
|
||||
|
||||
Ok(ws_stream)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use anyhow::Context;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
pub fn get_device_id() -> anyhow::Result<String> {
|
||||
match machine_uid::get() {
|
||||
Ok(id) => return Ok(id),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to get system ID: {}. Using fallback.", e);
|
||||
}
|
||||
}
|
||||
|
||||
get_fallback_id()
|
||||
}
|
||||
fn get_fallback_id() -> anyhow::Result<String> {
|
||||
let path = Path::new("device_id");
|
||||
|
||||
if let Ok(content) = fs::read_to_string(path) {
|
||||
let id = content.trim();
|
||||
if !id.is_empty() {
|
||||
return Ok(id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let new_id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
fs::write(path, &new_id).context("Failed to write device_id file")?;
|
||||
|
||||
Ok(new_id)
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use dns_parser::{Builder, Packet, QueryClass, QueryType, RData, ResponseCode};
|
||||
use rand::seq::SliceRandom;
|
||||
use std::io;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use rust_p2p_core::socket::LocalInterface;
|
||||
|
||||
pub async fn dns_query_txt(
|
||||
domain: &str,
|
||||
mut name_servers: Vec<String>,
|
||||
default_interface: &Option<LocalInterface>,
|
||||
) -> io::Result<Vec<String>> {
|
||||
let mut err: Option<io::Error> = None;
|
||||
if name_servers.is_empty() {
|
||||
name_servers.push("223.5.5.5:53".into());
|
||||
name_servers.push("114.114.114.114:53".into());
|
||||
}
|
||||
for name_server in name_servers {
|
||||
match txt_dns(domain, name_server, default_interface).await {
|
||||
Ok(addr) => {
|
||||
if !addr.is_empty() {
|
||||
return Ok(addr);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
err.replace(e);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some(e) = err {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(io::Error::other(format!("DNS query failed {domain:?}")))
|
||||
}
|
||||
}
|
||||
pub async fn dns_query_one(
|
||||
domain: &str,
|
||||
name_servers: &Vec<String>,
|
||||
default_interface: &Option<LocalInterface>,
|
||||
) -> anyhow::Result<SocketAddr> {
|
||||
let mut vec = dns_query_all(domain, name_servers, default_interface).await?;
|
||||
vec.shuffle(&mut rand::rng());
|
||||
vec.pop().context("DNS query failed")
|
||||
}
|
||||
pub async fn dns_query_all(
|
||||
domain: &str,
|
||||
name_servers: &Vec<String>,
|
||||
default_interface: &Option<LocalInterface>,
|
||||
) -> anyhow::Result<Vec<SocketAddr>> {
|
||||
match SocketAddr::from_str(domain) {
|
||||
Ok(addr) => Ok(vec![addr]),
|
||||
Err(_) => {
|
||||
if name_servers.is_empty() {
|
||||
let addrs: Vec<SocketAddr> = tokio::net::lookup_host(domain)
|
||||
.await
|
||||
.map_err(|e| io::Error::other(format!("DNS query failed: {domain:?},{e:?}")))?
|
||||
.collect();
|
||||
return Ok(addrs);
|
||||
}
|
||||
|
||||
let mut err: Option<io::Error> = None;
|
||||
for name_server in name_servers {
|
||||
let end_index = domain
|
||||
.rfind(':')
|
||||
.ok_or_else(|| io::Error::other(format!("not port: {domain:?}")))?;
|
||||
let host = &domain[..end_index];
|
||||
let port = u16::from_str(&domain[end_index + 1..])
|
||||
.map_err(|_| io::Error::other(format!("not port: {domain:?}")))?;
|
||||
let th1 = {
|
||||
let host = host.to_string();
|
||||
let name_server = name_server.clone();
|
||||
let default_interface = default_interface.clone();
|
||||
tokio::spawn(a_dns(host, name_server, default_interface.clone()))
|
||||
};
|
||||
let th2 = {
|
||||
let host = host.to_string();
|
||||
let name_server = name_server.clone();
|
||||
let default_interface = default_interface.clone();
|
||||
tokio::spawn(aaaa_dns(host, name_server, default_interface.clone()))
|
||||
};
|
||||
let mut addr = Vec::new();
|
||||
match th1.await? {
|
||||
Ok(rs) => {
|
||||
for ip in rs {
|
||||
addr.push(SocketAddr::new(ip.into(), port));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
err.replace(e);
|
||||
}
|
||||
}
|
||||
match th2.await? {
|
||||
Ok(rs) => {
|
||||
for ip in rs {
|
||||
addr.push(SocketAddr::new(ip.into(), port));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if addr.is_empty() {
|
||||
err.replace(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if addr.is_empty() {
|
||||
continue;
|
||||
}
|
||||
return Ok(addr);
|
||||
}
|
||||
if let Some(e) = err {
|
||||
Err(e.into())
|
||||
} else {
|
||||
Err(anyhow!("DNS query failed {domain:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn query<'a>(
|
||||
udp: &UdpSocket,
|
||||
domain: &str,
|
||||
name_server: SocketAddr,
|
||||
record_type: QueryType,
|
||||
buf: &'a mut [u8],
|
||||
) -> io::Result<Packet<'a>> {
|
||||
let mut builder = Builder::new_query(1, true);
|
||||
builder.add_question(domain, false, record_type, QueryClass::IN);
|
||||
let packet = builder.build().unwrap();
|
||||
|
||||
udp.connect(name_server).await?;
|
||||
let mut count = 0;
|
||||
let len = loop {
|
||||
udp.send(&packet).await?;
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(3), udp.recv(buf)).await {
|
||||
Ok(len) => {
|
||||
break len?;
|
||||
}
|
||||
Err(_) => {
|
||||
count += 1;
|
||||
if count < 3 {
|
||||
continue;
|
||||
}
|
||||
Err(io::Error::other(format!("DNS {name_server:?} recv error ")))?
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let pkt = Packet::parse(&buf[..len]).map_err(|e| {
|
||||
io::Error::other(format!(
|
||||
"domain {domain:?} DNS {name_server:?} data error: {e}"
|
||||
))
|
||||
})?;
|
||||
if pkt.header.response_code != ResponseCode::NoError {
|
||||
return Err(io::Error::other(format!(
|
||||
"response_code {} DNS {:?} domain {:?}",
|
||||
pkt.header.response_code, name_server, domain
|
||||
)));
|
||||
}
|
||||
if pkt.answers.is_empty() {
|
||||
return Err(io::Error::other(format!(
|
||||
"No records received DNS {name_server:?} domain {domain:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(pkt)
|
||||
}
|
||||
|
||||
pub async fn txt_dns(
|
||||
domain: &str,
|
||||
name_server: String,
|
||||
default_interface: &Option<LocalInterface>,
|
||||
) -> io::Result<Vec<String>> {
|
||||
let name_server: SocketAddr = name_server
|
||||
.parse()
|
||||
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
|
||||
let udp = bind_udp(name_server, default_interface)?;
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let message = query(&udp, domain, name_server, QueryType::TXT, &mut buf).await?;
|
||||
let mut rs = Vec::new();
|
||||
for record in message.answers {
|
||||
if let RData::TXT(txt) = record.data {
|
||||
for x in txt.iter() {
|
||||
let txt = std::str::from_utf8(x)
|
||||
.map_err(|_| io::Error::other("record type txt is not string"))?;
|
||||
rs.push(txt.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(rs)
|
||||
}
|
||||
|
||||
fn bind_udp(
|
||||
name_server: SocketAddr,
|
||||
default_interface: &Option<LocalInterface>,
|
||||
) -> io::Result<UdpSocket> {
|
||||
let addr: SocketAddr = if name_server.is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
.parse()
|
||||
.expect("valid IPv4 socket address literal")
|
||||
} else {
|
||||
"[::]:0".parse().expect("valid IPv6 socket address literal")
|
||||
};
|
||||
let socket = rust_p2p_core::socket::bind_udp(addr, default_interface.as_ref())?;
|
||||
UdpSocket::from_std(socket.into())
|
||||
}
|
||||
|
||||
pub async fn a_dns(
|
||||
domain: String,
|
||||
name_server: String,
|
||||
default_interface: Option<LocalInterface>,
|
||||
) -> io::Result<Vec<Ipv4Addr>> {
|
||||
let name_server: SocketAddr = name_server
|
||||
.parse()
|
||||
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
|
||||
let udp = bind_udp(name_server, &default_interface)?;
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let message = query(&udp, &domain, name_server, QueryType::A, &mut buf).await?;
|
||||
let mut rs = Vec::new();
|
||||
for record in message.answers {
|
||||
if let RData::A(a) = record.data {
|
||||
rs.push(a.0);
|
||||
}
|
||||
}
|
||||
Ok(rs)
|
||||
}
|
||||
|
||||
pub async fn aaaa_dns(
|
||||
domain: String,
|
||||
name_server: String,
|
||||
default_interface: Option<LocalInterface>,
|
||||
) -> io::Result<Vec<Ipv6Addr>> {
|
||||
let name_server: SocketAddr = name_server
|
||||
.parse()
|
||||
.map_err(|e| io::Error::other(format!("dns {name_server} is error :{e:?}")))?;
|
||||
let udp = bind_udp(name_server, &default_interface)?;
|
||||
let mut buf = vec![0u8; 65536];
|
||||
let message = query(&udp, &domain, name_server, QueryType::AAAA, &mut buf).await?;
|
||||
let mut rs = Vec::new();
|
||||
for record in message.answers {
|
||||
if let RData::AAAA(a) = record.data {
|
||||
rs.push(a.0);
|
||||
}
|
||||
}
|
||||
Ok(rs)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
pub mod device_id;
|
||||
pub(crate) mod dns_query;
|
||||
pub mod task_control;
|
||||
pub(crate) mod time {
|
||||
pub fn now_ts_ms() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tokio::sync::Notify;
|
||||
use tokio::task::{Id, JoinHandle};
|
||||
|
||||
struct TaskGroupState {
|
||||
stopped: bool,
|
||||
tasks: HashMap<Id, JoinHandle<()>>,
|
||||
}
|
||||
|
||||
struct TaskGroupInner {
|
||||
state: Mutex<TaskGroupState>,
|
||||
all_stopped_notify: Notify,
|
||||
}
|
||||
|
||||
impl TaskGroupInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
state: Mutex::new(TaskGroupState {
|
||||
stopped: false,
|
||||
tasks: HashMap::new(),
|
||||
}),
|
||||
all_stopped_notify: Notify::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn<F>(self: &Arc<Self>, f: F) -> Option<Id>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
let mut state = self.state.lock();
|
||||
if state.stopped {
|
||||
return None;
|
||||
}
|
||||
|
||||
let guard = TaskGuard {
|
||||
inner: Arc::downgrade(self),
|
||||
};
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let _guard = guard;
|
||||
f.await;
|
||||
});
|
||||
|
||||
let task_id = handle.id();
|
||||
state.tasks.insert(task_id, handle);
|
||||
Some(task_id)
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let mut state = self.state.lock();
|
||||
state.stopped = true;
|
||||
for (_, handle) in state.tasks.drain() {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
fn is_stopped(&self) -> bool {
|
||||
self.state.lock().stopped
|
||||
}
|
||||
|
||||
fn remove_task(&self, task_id: Id) {
|
||||
let all_stopped = {
|
||||
let mut state = self.state.lock();
|
||||
state.tasks.remove(&task_id);
|
||||
if state.tasks.is_empty() {
|
||||
state.stopped = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if all_stopped {
|
||||
self.all_stopped_notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
async fn abort_task(&self, task_id: Id) {
|
||||
let handle = self.state.lock().tasks.remove(&task_id);
|
||||
if let Some(handle) = handle {
|
||||
handle.abort();
|
||||
_ = handle.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn join_all(&self) {
|
||||
let tasks = std::mem::take(&mut self.state.lock().tasks);
|
||||
for (_, h) in tasks {
|
||||
let _ = h.await;
|
||||
}
|
||||
}
|
||||
|
||||
fn all_tasks_stopped(&self) -> bool {
|
||||
let state = self.state.lock();
|
||||
state.stopped && state.tasks.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TaskGroupInner {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskGuard {
|
||||
inner: Weak<TaskGroupInner>,
|
||||
}
|
||||
|
||||
impl Drop for TaskGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.inner.upgrade() {
|
||||
let task_id = tokio::task::id();
|
||||
inner.remove_task(task_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TaskGroup {
|
||||
inner: Arc<TaskGroupInner>,
|
||||
}
|
||||
|
||||
impl TaskGroup {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(TaskGroupInner::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
self.inner.stop();
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.inner.is_stopped()
|
||||
}
|
||||
|
||||
pub fn spawn<F>(&self, f: F) -> SubTask
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
match self.inner.spawn(f) {
|
||||
Some(task_id) => SubTask::new(task_id, Arc::downgrade(&self.inner)),
|
||||
None => SubTask::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn join_all(&self) {
|
||||
self.inner.join_all().await;
|
||||
}
|
||||
|
||||
pub async fn wait_all_stopped(&self) {
|
||||
loop {
|
||||
if self.inner.all_tasks_stopped() {
|
||||
return;
|
||||
}
|
||||
self.inner.all_stopped_notify.notified().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubTask {
|
||||
task_id: Option<Id>,
|
||||
inner: Weak<TaskGroupInner>,
|
||||
}
|
||||
|
||||
impl SubTask {
|
||||
fn new(task_id: Id, inner: Weak<TaskGroupInner>) -> Self {
|
||||
Self {
|
||||
task_id: Some(task_id),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
task_id: None,
|
||||
inner: Weak::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop(&self) {
|
||||
if let Some(task_id) = self.task_id
|
||||
&& let Some(inner) = self.inner.upgrade()
|
||||
{
|
||||
inner.abort_task(task_id).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
if let Some(task_id) = self.task_id
|
||||
&& let Some(inner) = self.inner.upgrade()
|
||||
{
|
||||
return inner.state.lock().tasks.contains_key(&task_id);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<Id> {
|
||||
self.task_id
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TaskGroupManager {
|
||||
task_group: Arc<Mutex<Option<TaskGroup>>>,
|
||||
}
|
||||
|
||||
impl TaskGroupManager {
|
||||
pub fn new() -> Self {
|
||||
TaskGroupManager::default()
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.task_group.lock().is_some()
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.task_group.lock().is_none()
|
||||
}
|
||||
|
||||
pub fn create_task(&self) -> anyhow::Result<(TaskGroup, TaskGroupGuard)> {
|
||||
let mut guard = self.task_group.lock();
|
||||
if guard.is_some() {
|
||||
anyhow::bail!("运行中")
|
||||
}
|
||||
|
||||
let task_group = TaskGroup::new();
|
||||
guard.replace(task_group.clone());
|
||||
let stop_guard = TaskGroupGuard {
|
||||
task_group: self.task_group.clone(),
|
||||
};
|
||||
Ok((task_group, stop_guard))
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
let option = self.task_group.lock();
|
||||
if let Some(task_group) = option.as_ref() {
|
||||
task_group.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct TaskGroupGuard {
|
||||
task_group: Arc<Mutex<Option<TaskGroup>>>,
|
||||
}
|
||||
impl Drop for TaskGroupGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(task_group) = self.task_group.lock().take() {
|
||||
task_group.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "vnt-ipc"
|
||||
version = "2.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
vnt-core = { path = "../vnt-core" }
|
||||
anyhow = "1.0.100"
|
||||
futures = "0.3.31"
|
||||
prost = "0.14.1"
|
||||
tokio = "1.48.0"
|
||||
tokio-util = "0.7.17"
|
||||
log = "0.4.29"
|
||||
console = "0.16.2"
|
||||
time = "0.3.44"
|
||||
cli-table = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.14"
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
let mut config = prost_build::Config::new();
|
||||
config.protoc_arg("--experimental_allow_proto3_optional");
|
||||
config
|
||||
.compile_protos(&["proto/local_ipc.proto"], &["proto"])
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package protocol.local_ipc;
|
||||
|
||||
message IpcRequest{
|
||||
oneof ipc_cmd{
|
||||
AppInfoCMD app_info = 1;
|
||||
ClientIpsCMD client_ips = 2;
|
||||
ClientListCMD client_list = 3;
|
||||
AllRouteCMD all_route = 4;
|
||||
}
|
||||
}
|
||||
message AppInfoCMD{
|
||||
|
||||
}
|
||||
message ClientIpsCMD{
|
||||
|
||||
}
|
||||
message ClientListCMD{
|
||||
|
||||
}
|
||||
message AllRouteCMD{
|
||||
|
||||
}
|
||||
|
||||
|
||||
message IpcResponse{
|
||||
oneof response_payload{
|
||||
AppInfo app_info = 1;
|
||||
ClientIpList client_ips = 2;
|
||||
ClientInfoList client_list = 3;
|
||||
ClientRouteList all_route = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message AppInfo{
|
||||
repeated ServerInfo server_info = 1;
|
||||
string name = 3;
|
||||
string version = 4;
|
||||
optional uint32 ip = 5;
|
||||
uint32 online_client_num = 6;
|
||||
uint32 offline_client_num = 7;
|
||||
uint32 direct_client_num = 8;
|
||||
string device_id = 11;
|
||||
optional string nat_type = 12;
|
||||
repeated string public_ipv4s = 13;
|
||||
optional string public_ipv6 = 14;
|
||||
}
|
||||
|
||||
message ServerInfo{
|
||||
string server = 1;
|
||||
bool connected = 2;
|
||||
optional uint32 server_rtt = 3;
|
||||
optional int64 last_connected_time = 4;
|
||||
}
|
||||
|
||||
message ClientInfoList{
|
||||
repeated ClientInfoItem items = 1;
|
||||
}
|
||||
|
||||
message ClientInfoItem{
|
||||
uint32 ip = 1;
|
||||
string name = 2;
|
||||
string version = 3;
|
||||
bool online = 4;
|
||||
bool is_direct = 5;
|
||||
int64 last_connected_time = 6;
|
||||
optional uint32 rtt = 7;
|
||||
bool key_equal = 8;
|
||||
optional PacketLoss packet_loss = 9;
|
||||
}
|
||||
|
||||
message PacketLoss{
|
||||
uint64 sent = 1;
|
||||
uint64 received = 2;
|
||||
double loss_rate = 3;
|
||||
}
|
||||
|
||||
message ClientRouteList{
|
||||
repeated ClientRouteItem items = 1;
|
||||
}
|
||||
|
||||
message ClientRouteItem{
|
||||
uint32 ip = 1;
|
||||
repeated Route route_list = 2;
|
||||
}
|
||||
|
||||
message Route{
|
||||
string addr = 1;
|
||||
uint32 metric = 2;
|
||||
uint32 rtt = 3;
|
||||
}
|
||||
|
||||
message ClientIpList{
|
||||
repeated ClientIpItem items = 1;
|
||||
}
|
||||
|
||||
message ClientIpItem{
|
||||
uint32 ip = 1;
|
||||
bool online = 2;
|
||||
bool is_direct = 3;
|
||||
optional uint32 rtt = 4;
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
use crate::message::ipc_request::IpcCmd;
|
||||
use crate::message::ipc_response::ResponsePayload;
|
||||
use crate::message::{
|
||||
AppInfo, ClientInfoList, ClientIpList, ClientRouteList, IpcRequest, IpcResponse,
|
||||
};
|
||||
use crate::{DEFAULT_PORT, get_port_file_path};
|
||||
use anyhow::Context;
|
||||
use cli_table::{Cell, Style, Table, print_stdout};
|
||||
use console::style;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use prost::Message;
|
||||
use std::fs;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::time::Duration;
|
||||
use time::macros::format_description;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
|
||||
pub async fn run_client(cmd: IpcCmd, port: Option<u16>) -> anyhow::Result<()> {
|
||||
let path = get_port_file_path();
|
||||
let port = if let Some(port) = port {
|
||||
port
|
||||
} else {
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(port_string) => port_string.trim().parse().context("parse port error")?,
|
||||
Err(_) => DEFAULT_PORT,
|
||||
}
|
||||
};
|
||||
|
||||
let addr = format!("127.0.0.1:{}", port);
|
||||
|
||||
let stream = tokio::time::timeout(Duration::from_secs(3), TcpStream::connect(&addr))
|
||||
.await
|
||||
.context("Connection timed out")??;
|
||||
|
||||
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
|
||||
|
||||
framed
|
||||
.send(IpcRequest { ipc_cmd: Some(cmd) }.encode_to_vec().into())
|
||||
.await?;
|
||||
let response = framed.next().await.context("Unexpected end of stream")??;
|
||||
let response = IpcResponse::decode(response).context("decode response error")?;
|
||||
match response
|
||||
.response_payload
|
||||
.context("response payload is empty")?
|
||||
{
|
||||
ResponsePayload::AppInfo(info) => {
|
||||
print_app_info(info);
|
||||
}
|
||||
ResponsePayload::ClientIps(list) => print_client_ip_list(list)?,
|
||||
ResponsePayload::ClientList(list) => {
|
||||
print_client_list(list)?;
|
||||
}
|
||||
ResponsePayload::AllRoute(route_list) => {
|
||||
print_route_list(route_list)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_style(s: &str) -> console::StyledObject<&str> {
|
||||
style(s).cyan().bright().bold()
|
||||
}
|
||||
pub fn print_app_info(info: AppInfo) {
|
||||
println!(
|
||||
"\n{}",
|
||||
style("--- Application Information ---").italic().dim()
|
||||
);
|
||||
|
||||
for server_info in info.server_info {
|
||||
let status_colored = if server_info.connected {
|
||||
if let Some(rtt) = server_info.server_rtt {
|
||||
style(format!("Online {rtt}ms"))
|
||||
.green()
|
||||
.bright()
|
||||
.to_string()
|
||||
} else {
|
||||
style("Online").green().bright().to_string()
|
||||
}
|
||||
} else {
|
||||
style("Offline").red().bright().to_string()
|
||||
};
|
||||
|
||||
println!(
|
||||
"{:24}{} ({})",
|
||||
key_style("Server:"),
|
||||
style(&server_info.server).white(),
|
||||
status_colored
|
||||
);
|
||||
|
||||
let time_str = server_info
|
||||
.last_connected_time
|
||||
.map(|v| style(ts_to_string(v / 1000)).white().to_string())
|
||||
.unwrap_or_else(|| "Never".to_string());
|
||||
|
||||
println!("{:24}{}", key_style("Last Connected Time:"), time_str);
|
||||
}
|
||||
|
||||
println!("{:24}{}", key_style("Name:"), style(&info.name).white());
|
||||
println!("{:24}{}", key_style("Id:"), style(&info.device_id).white());
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Version:"),
|
||||
style(&info.version).white()
|
||||
);
|
||||
|
||||
let ip_str = info
|
||||
.ip
|
||||
.map(|v| {
|
||||
style(Ipv4Addr::from(v).to_string())
|
||||
.yellow()
|
||||
.bright()
|
||||
.to_string()
|
||||
})
|
||||
.unwrap_or_else(|| style("N/A").red().bright().to_string());
|
||||
|
||||
println!("{:24}{}", key_style("IP:"), ip_str);
|
||||
|
||||
let total_clients = info.online_client_num + info.offline_client_num;
|
||||
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Total Clients:"),
|
||||
style(total_clients).color256(213)
|
||||
);
|
||||
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Online Clients:"),
|
||||
style(info.online_client_num).color256(82)
|
||||
);
|
||||
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("P2P Clients:"),
|
||||
style(info.direct_client_num).color256(117)
|
||||
);
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Nat Type:"),
|
||||
style(info.nat_type.unwrap_or_else(|| "Unknown".to_string())).color256(117)
|
||||
);
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Ipv6:"),
|
||||
style(info.public_ipv6.unwrap_or_default()).color256(117)
|
||||
);
|
||||
println!(
|
||||
"{:24}{}",
|
||||
key_style("Public Ipv4:"),
|
||||
style(info.public_ipv4s.join(",")).color256(117)
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
style("-------------------------------").italic().dim()
|
||||
);
|
||||
}
|
||||
|
||||
fn print_client_ip_list(list: ClientIpList) -> anyhow::Result<()> {
|
||||
println!("\n--- Client List ({}) ---", list.items.len());
|
||||
|
||||
let table = list
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
vec![
|
||||
Ipv4Addr::from(item.ip).to_string().cell(),
|
||||
item.online.to_string().cell(),
|
||||
item.is_direct.to_string().cell(),
|
||||
item.rtt.map(|v| v.to_string()).unwrap_or_default().cell(),
|
||||
]
|
||||
})
|
||||
.table()
|
||||
.title(vec![
|
||||
"IP".cell().bold(true),
|
||||
"Online".cell().bold(true),
|
||||
"P2P".cell().bold(true),
|
||||
"RTT".cell().bold(true),
|
||||
]);
|
||||
|
||||
print_stdout(table)?;
|
||||
println!("\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_client_list(list: ClientInfoList) -> anyhow::Result<()> {
|
||||
println!("\n--- Client List ({}) ---", list.items.len());
|
||||
|
||||
let table = list
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let key_equal: bool = item.key_equal;
|
||||
let mut ip_str = Ipv4Addr::from(item.ip).to_string();
|
||||
if !key_equal {
|
||||
ip_str.push_str("(Key Mismatch)");
|
||||
}
|
||||
let loss_str = item
|
||||
.packet_loss
|
||||
.as_ref()
|
||||
.map(|v| format!("{:.1}%", v.loss_rate))
|
||||
.unwrap_or_default();
|
||||
|
||||
vec![
|
||||
ip_str.cell(),
|
||||
item.name.clone().cell(),
|
||||
item.version.clone().cell(),
|
||||
item.online.to_string().cell(),
|
||||
item.is_direct.to_string().cell(),
|
||||
item.rtt.map(|v| v.to_string()).unwrap_or_default().cell(),
|
||||
loss_str.cell(),
|
||||
ts_to_string(item.last_connected_time).cell(),
|
||||
]
|
||||
})
|
||||
.table()
|
||||
.title(vec![
|
||||
"IP".cell().bold(true),
|
||||
"Name".cell().bold(true),
|
||||
"Version".cell().bold(true),
|
||||
"Online".cell().bold(true),
|
||||
"P2P".cell().bold(true),
|
||||
"RTT".cell().bold(true),
|
||||
"Loss".cell().bold(true),
|
||||
"Last Connected Time".cell().bold(true),
|
||||
]);
|
||||
|
||||
print_stdout(table)?;
|
||||
println!("\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_route_list(route_list: ClientRouteList) -> anyhow::Result<()> {
|
||||
let total_routes: usize = route_list
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.route_list.len())
|
||||
.sum();
|
||||
|
||||
println!("\n--- All Routes List (Total: {}) ---", total_routes);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for client_route in route_list.items {
|
||||
let client_ip_str = Ipv4Addr::from(client_route.ip).to_string();
|
||||
|
||||
for route in client_route.route_list {
|
||||
rows.push(vec![
|
||||
client_ip_str.clone().cell(),
|
||||
route.metric.to_string().cell(),
|
||||
route.rtt.to_string().cell(),
|
||||
route.addr.clone().cell(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
let table = rows.table().title(vec![
|
||||
"Destination IP".cell().bold(true),
|
||||
"Metric".cell().bold(true),
|
||||
"RTT (ms)".cell().bold(true),
|
||||
"Remote Address".cell().bold(true),
|
||||
]);
|
||||
|
||||
print_stdout(table)?;
|
||||
println!("\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ts_to_string(ts_secs: i64) -> String {
|
||||
let dt = match OffsetDateTime::from_unix_timestamp(ts_secs) {
|
||||
Ok(dt) => dt,
|
||||
Err(_) => {
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
let local_offset = match UtcOffset::local_offset_at(dt) {
|
||||
Ok(offset) => offset,
|
||||
Err(_e) => match UtcOffset::from_hms(8, 0, 0) {
|
||||
Ok(offset) => offset,
|
||||
Err(_) => return String::new(),
|
||||
},
|
||||
};
|
||||
let dt_local = dt.to_offset(local_offset);
|
||||
let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
dt_local.format(&format).unwrap()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
pub mod client;
|
||||
pub mod message;
|
||||
pub mod server;
|
||||
|
||||
pub use vnt_core::*;
|
||||
const DEFAULT_PORT: u16 = 11233;
|
||||
|
||||
const PORT_FILE: &str = "PORT";
|
||||
|
||||
fn get_port_file_path() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(PORT_FILE)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.local_ipc.rs"));
|
||||
}
|
||||
pub use proto::*;
|
||||
@@ -0,0 +1,194 @@
|
||||
use crate::{DEFAULT_PORT, get_port_file_path};
|
||||
|
||||
use crate::message::ipc_request::IpcCmd;
|
||||
use crate::message::ipc_response::ResponsePayload;
|
||||
use crate::message::{
|
||||
AppInfo, ClientInfoItem, ClientInfoList, ClientIpItem, ClientIpList, ClientRouteItem,
|
||||
ClientRouteList, IpcRequest, IpcResponse, PacketLoss, Route, ServerInfo,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use prost::Message;
|
||||
use std::fs;
|
||||
use std::net::Ipv4Addr;
|
||||
use tokio::io::{self};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
use vnt_core::api::VntApi;
|
||||
|
||||
async fn handle_connection(stream: TcpStream, vnt_api: VntApi) -> anyhow::Result<()> {
|
||||
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
|
||||
|
||||
if let Some(Ok(message)) = framed.next().await {
|
||||
let request = IpcRequest::decode(message.as_ref())?;
|
||||
let Some(cmd) = request.ipc_cmd else {
|
||||
bail!("Received an IpcRequest but it was None");
|
||||
};
|
||||
let rs = match cmd {
|
||||
IpcCmd::AppInfo(_) => {
|
||||
let info = app_info(&vnt_api);
|
||||
ResponsePayload::AppInfo(info)
|
||||
}
|
||||
IpcCmd::ClientIps(_) => {
|
||||
let items = vnt_api
|
||||
.client_ips()
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let rtt = if let Some(v) = vnt_api.find_route(&v.ip) {
|
||||
Some(v.rtt())
|
||||
} else {
|
||||
vnt_api.server_node_rtt(&v.ip).map(|v| v * 2)
|
||||
};
|
||||
ClientIpItem {
|
||||
ip: v.ip.into(),
|
||||
online: v.online,
|
||||
is_direct: vnt_api.is_direct(&v.ip),
|
||||
rtt,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
ResponsePayload::ClientIps(ClientIpList { items })
|
||||
}
|
||||
IpcCmd::ClientList(_) => {
|
||||
let client_list = vnt_api.server_rpc().client_list().await?;
|
||||
let key_sign = vnt_api.get_config().and_then(|config| config.key_sign());
|
||||
let items = client_list
|
||||
.list
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let ip = Ipv4Addr::from(v.ip);
|
||||
let rtt = if let Some(v) = vnt_api.find_route(&ip) {
|
||||
Some(v.rtt())
|
||||
} else {
|
||||
vnt_api.server_node_rtt(&ip).map(|v| v * 2)
|
||||
};
|
||||
let packet_loss = vnt_api.packet_loss_info(&ip).map(|info| PacketLoss {
|
||||
sent: info.sent,
|
||||
received: info.received,
|
||||
loss_rate: info.loss_rate,
|
||||
});
|
||||
ClientInfoItem {
|
||||
ip: v.ip,
|
||||
name: v.name,
|
||||
version: v.version,
|
||||
online: v.online,
|
||||
is_direct: vnt_api.is_direct(&Ipv4Addr::from(v.ip)),
|
||||
last_connected_time: v.last_connected_time,
|
||||
rtt,
|
||||
key_equal: key_sign == v.key_sign,
|
||||
packet_loss,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
ResponsePayload::ClientList(ClientInfoList { items })
|
||||
}
|
||||
IpcCmd::AllRoute(_) => {
|
||||
let route_list = all_route(&vnt_api);
|
||||
ResponsePayload::AllRoute(route_list)
|
||||
}
|
||||
};
|
||||
let rs = IpcResponse {
|
||||
response_payload: Some(rs),
|
||||
};
|
||||
framed.send(rs.encode_to_vec().into()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn app_info(vnt_api: &VntApi) -> AppInfo {
|
||||
let config = vnt_api.get_config();
|
||||
let ips = vnt_api.client_ips();
|
||||
let online_client_num = ips.iter().filter(|v| v.online).count() as _;
|
||||
let offline_client_num = ips.iter().filter(|v| !v.online).count() as _;
|
||||
let direct_client_num = ips.iter().filter(|ip| vnt_api.is_direct(&ip.ip)).count() as _;
|
||||
let server_node_list = vnt_api.server_node_list();
|
||||
let nat_info = vnt_api.nat_info();
|
||||
AppInfo {
|
||||
server_info: server_node_list
|
||||
.into_iter()
|
||||
.map(|v| ServerInfo {
|
||||
server: v.server_addr.to_string(),
|
||||
connected: v.connected,
|
||||
server_rtt: v.rtt,
|
||||
last_connected_time: v.last_connected_time,
|
||||
})
|
||||
.collect(),
|
||||
name: config
|
||||
.as_ref()
|
||||
.map(|v| v.device_name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
ip: vnt_api.network().map(|v| v.ip.into()),
|
||||
online_client_num,
|
||||
offline_client_num,
|
||||
direct_client_num,
|
||||
device_id: config.map(|v| v.device_id.clone()).unwrap_or_default(),
|
||||
nat_type: nat_info.as_ref().map(|v| format!("{:?}", v.nat_type)),
|
||||
public_ipv4s: nat_info
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
v.public_ips
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
public_ipv6: nat_info
|
||||
.as_ref()
|
||||
.and_then(|v| v.ipv6.map(|v| v.to_string())),
|
||||
}
|
||||
}
|
||||
fn all_route(vnt_api: &VntApi) -> ClientRouteList {
|
||||
let vec = vnt_api.route_table();
|
||||
let mut items = Vec::with_capacity(vec.len());
|
||||
for (ip, route_list) in vec {
|
||||
items.push(ClientRouteItem {
|
||||
ip: ip.into(),
|
||||
route_list: route_list
|
||||
.into_iter()
|
||||
.map(|v| Route {
|
||||
addr: v.route_key().to_string(),
|
||||
metric: v.metric() as _,
|
||||
rtt: v.rtt(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
ClientRouteList { items }
|
||||
}
|
||||
|
||||
pub async fn run_server(bind_port: Option<u16>, vnt_api: VntApi) -> anyhow::Result<()> {
|
||||
let mut port = bind_port.unwrap_or(DEFAULT_PORT);
|
||||
let listener;
|
||||
|
||||
loop {
|
||||
match TcpListener::bind(format!("127.0.0.1:{}", port)).await {
|
||||
Ok(l) => {
|
||||
listener = l;
|
||||
break;
|
||||
}
|
||||
Err(e) if bind_port.is_none() && e.kind() == io::ErrorKind::AddrInUse => {
|
||||
port = 0;
|
||||
}
|
||||
Err(e) => bail!("bind :{e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
let bound_addr = listener.local_addr()?;
|
||||
log::info!("IPC Listening on {}", bound_addr);
|
||||
let actual_port = bound_addr.port();
|
||||
|
||||
let path = get_port_file_path();
|
||||
fs::write(&path, actual_port.to_string())?;
|
||||
|
||||
loop {
|
||||
let (stream, peer_addr) = listener.accept().await?;
|
||||
log::info!("IPC Connection from {}", peer_addr);
|
||||
let vnt_api = vnt_api.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_connection(stream, vnt_api).await {
|
||||
log::warn!("IPC Error handling client: {:?},peer_addr={peer_addr}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "vnt-web"
|
||||
version = "2.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
vnt-core = { path = "../vnt-core" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
axum = "0.8.8"
|
||||
tower-http = { version = "0.6", features = ["fs", "cors", "trace"] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0.100"
|
||||
log = "0.4.29"
|
||||
ipnet = "2.11.0"
|
||||
hostname = "0.4.2"
|
||||
route_manager = "0.2.11"
|
||||
toml = "0.9.8"
|
||||
rust-embed = "8.0"
|
||||
mime_guess = "2.0"
|
||||
parking_lot = "0.12"
|
||||
time = { version = "0.3.45", features = ["local-offset", "formatting", "macros"] }
|
||||
@@ -0,0 +1,17 @@
|
||||
mod service_http;
|
||||
|
||||
pub use service_http::run_http_server;
|
||||
|
||||
struct ScopeGuard<F: FnOnce()>(Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for ScopeGuard<F> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(f) = self.0.take() {
|
||||
f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn defer<F: FnOnce()>(f: F) -> ScopeGuard<F> {
|
||||
ScopeGuard(Some(f))
|
||||
}
|
||||
@@ -0,0 +1,1105 @@
|
||||
use crate::defer;
|
||||
use anyhow::{Context, anyhow};
|
||||
use axum::body::Body;
|
||||
use axum::http::{HeaderMap, HeaderValue, StatusCode, Uri, header};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Query, Request, State},
|
||||
middleware,
|
||||
response::Response,
|
||||
routing::{get, post},
|
||||
};
|
||||
use ipnet::Ipv4Net;
|
||||
use mime_guess::from_path;
|
||||
use parking_lot::Mutex;
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
use time::{OffsetDateTime, format_description};
|
||||
use tokio::fs;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use vnt_core::api::VntApi;
|
||||
use vnt_core::context::config::Config as CoreConfig;
|
||||
use vnt_core::core::{DEFAULT_MTU, NetworkManager};
|
||||
use vnt_core::nat::NetInput;
|
||||
use vnt_core::port_mapping::PortMapping;
|
||||
use vnt_core::tls::verifier::CertValidationMode;
|
||||
use vnt_core::tunnel_core::server::transport::config::ProtocolAddress;
|
||||
use vnt_core::utils::task_control::TaskGroupManager;
|
||||
|
||||
const CONFIG_DIR: &str = "vnt_config";
|
||||
const CURRENT_CONFIG_RECORD: &str = "vnt_current_config.txt";
|
||||
|
||||
#[derive(Serialize, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum VntStatus {
|
||||
#[default]
|
||||
Stopped,
|
||||
Starting,
|
||||
Running,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HttpAppState {
|
||||
task_group_manager: TaskGroupManager,
|
||||
inner: Arc<Mutex<HttpAppStateInner>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HttpAppStateInner {
|
||||
vnt: Option<VntHandler>,
|
||||
status: VntStatus,
|
||||
start_logs: Vec<String>,
|
||||
}
|
||||
|
||||
impl HttpAppState {
|
||||
fn starting(&self) -> anyhow::Result<()> {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.status != VntStatus::Stopped {
|
||||
return Err(anyhow!("VNT is already starting or running"));
|
||||
}
|
||||
if inner.vnt.is_some() {
|
||||
return Err(anyhow!("VNT is already running"));
|
||||
}
|
||||
inner.status = VntStatus::Starting;
|
||||
inner.start_logs.clear();
|
||||
Ok(())
|
||||
}
|
||||
fn stopped(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.vnt.take();
|
||||
inner.status = VntStatus::Stopped;
|
||||
}
|
||||
fn starting_to_stopped(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.status != VntStatus::Starting {
|
||||
return;
|
||||
}
|
||||
inner.vnt.take();
|
||||
inner.status = VntStatus::Stopped;
|
||||
inner
|
||||
.start_logs
|
||||
.push(format!("[{}] 启动中断", HttpAppState::timestamp()));
|
||||
}
|
||||
fn starting_to_running(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.status != VntStatus::Starting {
|
||||
log::error!("starting_to_running VNT is not starting");
|
||||
return;
|
||||
}
|
||||
inner.status = VntStatus::Running;
|
||||
inner.start_logs.clear();
|
||||
}
|
||||
|
||||
fn record_log(&self, msg: impl Into<String>) {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.status != VntStatus::Starting {
|
||||
return;
|
||||
}
|
||||
inner
|
||||
.start_logs
|
||||
.push(format!("[{}] {}", Self::timestamp(), msg.into()));
|
||||
}
|
||||
fn record_log_and_stopped(&self, msg: impl Into<String>) {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.status != VntStatus::Starting {
|
||||
return;
|
||||
}
|
||||
inner
|
||||
.start_logs
|
||||
.push(format!("[{}] {}", Self::timestamp(), msg.into()));
|
||||
inner.status = VntStatus::Stopped;
|
||||
}
|
||||
fn status(&self) -> VntStatus {
|
||||
self.inner.lock().status
|
||||
}
|
||||
|
||||
fn timestamp() -> String {
|
||||
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
|
||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||
now.format(&format)
|
||||
.unwrap_or_else(|_| "00:00:00".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct VntHandler {
|
||||
api: VntApi,
|
||||
config_name: String,
|
||||
config_file_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ApiResponse<T> {
|
||||
code: i32,
|
||||
msg: String,
|
||||
data: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> ApiResponse<T> {
|
||||
fn success(data: T) -> Self {
|
||||
Self {
|
||||
code: 0,
|
||||
msg: "success".to_string(),
|
||||
data: Some(data),
|
||||
}
|
||||
}
|
||||
|
||||
fn error(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
code: -1,
|
||||
msg: msg.into(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct StartConfig {
|
||||
pub config_name: Option<String>,
|
||||
pub server: Vec<String>,
|
||||
pub cert_mode: Option<String>,
|
||||
pub network_code: String,
|
||||
pub device_id: Option<String>,
|
||||
pub device_name: Option<String>,
|
||||
pub tun_name: Option<String>,
|
||||
pub ip: Option<Ipv4Addr>,
|
||||
pub password: Option<String>,
|
||||
#[serde(default)]
|
||||
pub no_punch: bool,
|
||||
#[serde(default)]
|
||||
pub compress: bool,
|
||||
#[serde(default)]
|
||||
pub rtx: bool,
|
||||
#[serde(default)]
|
||||
pub fec: bool,
|
||||
#[serde(default)]
|
||||
pub input: Vec<NetInput>,
|
||||
#[serde(default)]
|
||||
pub output: Vec<Ipv4Net>,
|
||||
#[serde(default)]
|
||||
pub no_nat: bool,
|
||||
#[serde(default)]
|
||||
pub no_tun: bool,
|
||||
pub mtu: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub port_mapping: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub allow_mapping: bool,
|
||||
#[serde(default)]
|
||||
pub udp_stun: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub tcp_stun: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SaveConfigReq {
|
||||
file_name: Option<String>,
|
||||
config: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FileReq {
|
||||
file_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ConfigSummary {
|
||||
file_name: String,
|
||||
config_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct HttpAppInfo {
|
||||
name: String,
|
||||
version: String,
|
||||
ip: Option<Ipv4Addr>,
|
||||
prefix_len: Option<u8>,
|
||||
gateway: Option<Ipv4Addr>,
|
||||
device_id: String,
|
||||
status: VntStatus,
|
||||
current_config_name: Option<String>,
|
||||
current_config_file: Option<String>,
|
||||
online_client_num: usize,
|
||||
offline_client_num: usize,
|
||||
direct_client_num: usize,
|
||||
server_info: Vec<HttpServerInfo>,
|
||||
nat_type: Option<String>,
|
||||
public_ipv6: Option<Ipv6Addr>,
|
||||
public_ipv4s: Vec<Ipv4Addr>,
|
||||
network_code: Option<String>,
|
||||
mtu: Option<u16>,
|
||||
fec: Option<bool>,
|
||||
compress: Option<bool>,
|
||||
encrypt: Option<bool>,
|
||||
rtx: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpServerInfo {
|
||||
server: String,
|
||||
connected: bool,
|
||||
server_rtt: Option<u32>,
|
||||
server_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpClientItem {
|
||||
ip: Ipv4Addr,
|
||||
name: Option<String>,
|
||||
online: bool,
|
||||
route: Option<HttpRouteDetail>,
|
||||
version: String,
|
||||
last_connected_time: i64,
|
||||
key_equal: i32,
|
||||
nat_info: Option<HttpClientNatInfo>,
|
||||
packet_loss: Option<HttpPacketLoss>,
|
||||
traffic: Option<HttpTraffic>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpClientNatInfo {
|
||||
nat_type: String,
|
||||
public_ips: Vec<Ipv4Addr>,
|
||||
ipv6: Option<Ipv6Addr>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpPacketLoss {
|
||||
sent: u64,
|
||||
received: u64,
|
||||
loss_rate: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpTraffic {
|
||||
tx_bytes: u64,
|
||||
rx_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpRouteItem {
|
||||
ip: Ipv4Addr,
|
||||
routes: Vec<HttpRouteDetail>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HttpRouteDetail {
|
||||
addr: String,
|
||||
protocol: String,
|
||||
metric: u8,
|
||||
rtt: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StartStatusResponse {
|
||||
status: VntStatus,
|
||||
logs: Vec<String>,
|
||||
}
|
||||
|
||||
async fn get_start_status(
|
||||
State(state): State<HttpAppState>,
|
||||
) -> Json<ApiResponse<StartStatusResponse>> {
|
||||
let lock = state.inner.lock();
|
||||
Json(ApiResponse::success(StartStatusResponse {
|
||||
status: lock.status,
|
||||
logs: lock.start_logs.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn logging_middleware(req: Request, next: axum::middleware::Next) -> Response {
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
let start = Instant::now();
|
||||
let response = next.run(req).await;
|
||||
log::info!(
|
||||
"Request: {} {} | Status: {} | Took: {:?}",
|
||||
method,
|
||||
uri,
|
||||
response.status(),
|
||||
start.elapsed()
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "static/"]
|
||||
struct Asset;
|
||||
|
||||
pub async fn run_http_server(
|
||||
addr: SocketAddr,
|
||||
start_config_file_name: Option<PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
fs::create_dir_all(CONFIG_DIR)
|
||||
.await
|
||||
.context("Failed to create config directory")?;
|
||||
|
||||
let state = HttpAppState {
|
||||
task_group_manager: TaskGroupManager::new(),
|
||||
inner: Arc::new(Default::default()),
|
||||
};
|
||||
|
||||
// 自动启动逻辑
|
||||
let auto_start_file = determine_auto_start_file(start_config_file_name).await;
|
||||
|
||||
if let Some((file_name, path)) = auto_start_file {
|
||||
log::info!("Auto starting VNT with config: {:?}", path);
|
||||
let state_clone = state.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = start_vnt_internal(&state_clone, file_name, path).await {
|
||||
log::error!("Auto start failed: {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/info", get(get_info))
|
||||
.route("/api/peers", get(get_peers))
|
||||
.route("/api/routes", get(get_routes))
|
||||
.route("/api/start/status", get(get_start_status))
|
||||
.route("/api/start", post(start_vnt_handler))
|
||||
.route("/api/stop", post(stop_vnt_handler))
|
||||
.route("/api/restart", post(restart_vnt_handler))
|
||||
.route("/api/config/list", get(list_configs))
|
||||
.route(
|
||||
"/api/config",
|
||||
get(get_config).post(save_config).delete(delete_config),
|
||||
)
|
||||
.layer(cors)
|
||||
.layer(middleware::from_fn(logging_middleware))
|
||||
.with_state(state)
|
||||
.fallback(static_handler);
|
||||
|
||||
log::info!("HTTP API Listening on http://{}", addr);
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 确定自动启动的配置文件
|
||||
async fn determine_auto_start_file(
|
||||
start_config_file_name: Option<PathBuf>,
|
||||
) -> Option<(String, PathBuf)> {
|
||||
let path = if let Some(name) = start_config_file_name {
|
||||
Some(name)
|
||||
} else if Path::new(CURRENT_CONFIG_RECORD).exists() {
|
||||
fs::read_to_string(CURRENT_CONFIG_RECORD)
|
||||
.await
|
||||
.ok()
|
||||
.filter(|content| !content.trim().is_empty())
|
||||
.map(|content| Path::new(CONFIG_DIR).join(content.trim()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
path.and_then(|p| {
|
||||
let file_name = p.file_name()?.to_str()?.to_string();
|
||||
if p.exists() {
|
||||
Some((file_name, p))
|
||||
} else {
|
||||
log::warn!("Auto start config file not found: {:?}", p);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build_headers_for_path(path: &str) -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
let is_gz = path.ends_with(".gz");
|
||||
|
||||
let mime = if is_gz {
|
||||
let original = path.trim_end_matches(".gz");
|
||||
from_path(original).first_or_octet_stream()
|
||||
} else {
|
||||
from_path(path).first_or_octet_stream()
|
||||
};
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(mime.as_ref()).unwrap(),
|
||||
);
|
||||
|
||||
if is_gz {
|
||||
headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip"));
|
||||
headers.insert(header::VARY, HeaderValue::from_static("Accept-Encoding"));
|
||||
}
|
||||
headers.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=31536000, immutable"),
|
||||
);
|
||||
headers
|
||||
}
|
||||
async fn static_handler(uri: Uri) -> impl IntoResponse {
|
||||
let path = uri.path().trim_start_matches('/');
|
||||
let path = if path.is_empty() { "index.html" } else { path };
|
||||
|
||||
// 先尝试从本地文件读取
|
||||
let local_path = Path::new("static").join(path);
|
||||
if local_path.is_file()
|
||||
&& let Ok(content) = tokio::fs::read(&local_path).await
|
||||
{
|
||||
log::debug!("Serving file from local filesystem: {:?}", local_path);
|
||||
let mime = from_path(&local_path).first_or_octet_stream();
|
||||
return ([(header::CONTENT_TYPE, mime.as_ref())], content).into_response();
|
||||
}
|
||||
|
||||
// 从内嵌数据中读取
|
||||
if let Some(content) = Asset::get(path) {
|
||||
log::debug!("Serving file from embedded assets: {}", path);
|
||||
let headers = build_headers_for_path(path);
|
||||
return (headers, Body::from(content.data)).into_response();
|
||||
}
|
||||
|
||||
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
|
||||
}
|
||||
|
||||
/// 启动 VNT 服务的入口函数
|
||||
async fn start_vnt_internal(
|
||||
state: &HttpAppState,
|
||||
file_name: String,
|
||||
file_path: PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
log::info!("Starting VNT service: {}", file_name);
|
||||
state.starting()?;
|
||||
|
||||
let state_for_error = state.clone();
|
||||
let on_error_guard = defer(move || {
|
||||
state_for_error.starting_to_stopped();
|
||||
});
|
||||
|
||||
state.record_log(format!("启动配置: {}", file_name));
|
||||
state.record_log("读取配置文件");
|
||||
|
||||
// 读取并解析配置
|
||||
let content = fs::read_to_string(&file_path)
|
||||
.await
|
||||
.with_context(|| format!("Config file not found: {:?}", file_path))?;
|
||||
|
||||
state.record_log("解析配置文件内容");
|
||||
let cfg: StartConfig = toml::from_str(&content).context("Failed to parse TOML config")?;
|
||||
|
||||
let config_display_name = cfg.config_name.clone().unwrap_or_else(|| file_name.clone());
|
||||
let core_config = convert_config(cfg)?;
|
||||
let sub_input = core_config.input.clone();
|
||||
|
||||
state.record_log("创建异步任务组");
|
||||
let (task_group, task_group_guard) = state
|
||||
.task_group_manager
|
||||
.create_task()
|
||||
.context("Create task failed")?;
|
||||
|
||||
state.record_log("创建组网管理器");
|
||||
|
||||
let state_clone = state.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = start_vnt_network(
|
||||
state_clone.clone(),
|
||||
file_name,
|
||||
config_display_name,
|
||||
core_config,
|
||||
sub_input,
|
||||
task_group,
|
||||
task_group_guard,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!("Failed to start VNT network: {:?}", e);
|
||||
state_clone.record_log_and_stopped(format!("启动失败: {}", e));
|
||||
}
|
||||
drop(on_error_guard);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 执行实际的网络启动操作
|
||||
async fn start_vnt_network(
|
||||
state: HttpAppState,
|
||||
file_name: String,
|
||||
config_display_name: String,
|
||||
core_config: CoreConfig,
|
||||
sub_input: Vec<NetInput>,
|
||||
task_group: vnt_core::utils::task_control::TaskGroup,
|
||||
task_group_guard: vnt_core::utils::task_control::TaskGroupGuard,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut network_manager = NetworkManager::create_network(Box::new(core_config), task_group.clone())
|
||||
.await
|
||||
.map_err(|e| anyhow!("Create network failed: {:?}", e))?;
|
||||
|
||||
let vnt_api = network_manager.vnt_api();
|
||||
|
||||
{
|
||||
let mut lock = state.inner.lock();
|
||||
if lock.vnt.is_some() {
|
||||
return Err(anyhow!("VNT is already running"));
|
||||
}
|
||||
lock.vnt = Some(VntHandler {
|
||||
api: vnt_api,
|
||||
config_name: config_display_name,
|
||||
config_file_name: file_name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let state_for_vnt_cleanup = state.clone();
|
||||
let vnt_cleanup_guard = defer(move || {
|
||||
state_for_vnt_cleanup.stopped();
|
||||
});
|
||||
|
||||
state.record_log("连接服务器,执行注册");
|
||||
log::info!("Registering with server");
|
||||
|
||||
let reg_msg = network_manager
|
||||
.register()
|
||||
.await
|
||||
.context("Registration failed")?;
|
||||
|
||||
state.record_log(format!("注册成功 {}/{}", reg_msg.ip, reg_msg.prefix_len));
|
||||
log::info!("Network Started: {}/{}", reg_msg.ip, reg_msg.prefix_len);
|
||||
if !network_manager.is_no_tun() {
|
||||
state.record_log("正在创建 TUN 虚拟网卡");
|
||||
network_manager.start_tun().await?;
|
||||
|
||||
state.record_log("创建 TUN 虚拟网卡成功,设置 IP");
|
||||
network_manager
|
||||
.set_network_ip(reg_msg.ip, reg_msg.prefix_len)
|
||||
.await?;
|
||||
state.record_log("设置 IP 成功");
|
||||
|
||||
// 配置子网路由
|
||||
if !sub_input.is_empty()
|
||||
&& let Ok(if_index) = network_manager.tun_if_index().await
|
||||
&& let Ok(mut route_manager) = route_manager::RouteManager::new()
|
||||
{
|
||||
state.record_log("配置子网路由");
|
||||
for input in &sub_input {
|
||||
let route =
|
||||
route_manager::Route::new(input.net.network().into(), input.net.prefix_len())
|
||||
.with_gateway(input.target_ip.into())
|
||||
.with_if_index(if_index);
|
||||
|
||||
if let Err(e) = route_manager.add(&route) {
|
||||
log::error!("add route [{route}] error: {e:?}");
|
||||
} else {
|
||||
log::info!("add route [{route}] successful");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.starting_to_running();
|
||||
|
||||
// 启动网络管理任务
|
||||
task_group.spawn(async move {
|
||||
network_manager.wait_all_stopped().await;
|
||||
drop(task_group_guard);
|
||||
drop(network_manager);
|
||||
drop(vnt_cleanup_guard);
|
||||
log::info!("Network manager stopped.");
|
||||
});
|
||||
|
||||
// 记录当前配置
|
||||
if let Err(e) = fs::write(CURRENT_CONFIG_RECORD, &file_name).await {
|
||||
log::warn!("Failed to record current config: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_valid_file_name(file_name: &str) -> bool {
|
||||
!file_name.is_empty()
|
||||
&& !file_name.contains("..")
|
||||
&& !file_name.contains('/')
|
||||
&& !file_name.contains('\\')
|
||||
}
|
||||
|
||||
async fn start_vnt_handler(
|
||||
State(state): State<HttpAppState>,
|
||||
Json(req): Json<FileReq>,
|
||||
) -> Json<ApiResponse<()>> {
|
||||
if !is_valid_file_name(&req.file_name) {
|
||||
return Json(ApiResponse::error("Invalid file name"));
|
||||
}
|
||||
|
||||
let path = Path::new(CONFIG_DIR).join(&req.file_name);
|
||||
if !path.exists() {
|
||||
return Json(ApiResponse::error("Config file not found"));
|
||||
}
|
||||
|
||||
match start_vnt_internal(&state, req.file_name, path).await {
|
||||
Ok(_) => Json(ApiResponse::success(())),
|
||||
Err(e) => Json(ApiResponse::error(format!("Start failed: {:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stop_vnt_handler(State(state): State<HttpAppState>) -> Json<ApiResponse<()>> {
|
||||
if state.status() == VntStatus::Stopped {
|
||||
return Json(ApiResponse::error("Vnt stopped"));
|
||||
}
|
||||
state.task_group_manager.stop();
|
||||
|
||||
let _ = fs::write(CURRENT_CONFIG_RECORD, "").await;
|
||||
Json(ApiResponse::success(()))
|
||||
}
|
||||
|
||||
async fn restart_vnt_handler(
|
||||
State(state): State<HttpAppState>,
|
||||
Json(req): Json<FileReq>,
|
||||
) -> Json<ApiResponse<()>> {
|
||||
if !is_valid_file_name(&req.file_name) {
|
||||
return Json(ApiResponse::error("Invalid file name"));
|
||||
}
|
||||
|
||||
let path = Path::new(CONFIG_DIR).join(&req.file_name);
|
||||
if !path.exists() {
|
||||
return Json(ApiResponse::error("Config file not found"));
|
||||
}
|
||||
|
||||
// 先停止(如果正在运行则停止,否则忽略)
|
||||
if state.status() != VntStatus::Stopped {
|
||||
state.task_group_manager.stop();
|
||||
// 等待停止完成
|
||||
for _ in 0..50 {
|
||||
if state.status() == VntStatus::Stopped {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 再启动
|
||||
match start_vnt_internal(&state, req.file_name, path).await {
|
||||
Ok(_) => Json(ApiResponse::success(())),
|
||||
Err(e) => Json(ApiResponse::error(format!("Restart failed: {:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_info(State(state): State<HttpAppState>) -> Json<ApiResponse<HttpAppInfo>> {
|
||||
let lock = state.inner.lock();
|
||||
let status = lock.status;
|
||||
|
||||
let info = if let Some(handler) = lock.vnt.as_ref() {
|
||||
let api = &handler.api;
|
||||
let config = api.get_config();
|
||||
let ips = api.client_ips();
|
||||
let server_node_list = api.server_node_list();
|
||||
let nat_info = api.nat_info();
|
||||
let network = api.network();
|
||||
|
||||
HttpAppInfo {
|
||||
name: config
|
||||
.as_ref()
|
||||
.map(|v| v.device_name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
ip: network.map(|v| v.ip),
|
||||
prefix_len: network.map(|v| v.prefix_len),
|
||||
gateway: network.map(|v| v.gateway),
|
||||
device_id: config
|
||||
.as_ref()
|
||||
.map(|v| v.device_id.clone())
|
||||
.unwrap_or_default(),
|
||||
status,
|
||||
current_config_name: Some(handler.config_name.clone()),
|
||||
current_config_file: Some(handler.config_file_name.clone()),
|
||||
online_client_num: ips.iter().filter(|v| v.online).count(),
|
||||
offline_client_num: ips.iter().filter(|v| !v.online).count(),
|
||||
direct_client_num: ips.iter().filter(|ip| api.is_direct(&ip.ip)).count(),
|
||||
server_info: server_node_list
|
||||
.into_iter()
|
||||
.map(|v| HttpServerInfo {
|
||||
server: v.server_addr.to_string(),
|
||||
connected: v.connected,
|
||||
server_rtt: v.rtt,
|
||||
server_version: v.server_version,
|
||||
})
|
||||
.collect(),
|
||||
nat_type: nat_info.as_ref().map(|v| format!("{:?}", v.nat_type)),
|
||||
public_ipv4s: nat_info
|
||||
.as_ref()
|
||||
.map(|v| v.public_ips.clone())
|
||||
.unwrap_or_default(),
|
||||
public_ipv6: nat_info.as_ref().and_then(|v| v.ipv6),
|
||||
network_code: config.as_ref().map(|v| v.network_code.clone()),
|
||||
mtu: config.as_ref().map(|v| v.mtu.unwrap_or(DEFAULT_MTU)),
|
||||
fec: config.as_ref().map(|v| v.fec),
|
||||
compress: config.as_ref().map(|v| v.compress),
|
||||
encrypt: config.as_ref().map(|v| v.password.is_some()),
|
||||
rtx: config.as_ref().map(|v| v.rtx),
|
||||
}
|
||||
} else {
|
||||
HttpAppInfo {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
status,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
Json(ApiResponse::success(info))
|
||||
}
|
||||
|
||||
async fn list_configs() -> Json<ApiResponse<Vec<ConfigSummary>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let Ok(mut entries) = fs::read_dir(CONFIG_DIR).await else {
|
||||
return Json(ApiResponse::success(result));
|
||||
};
|
||||
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
let path = entry.path();
|
||||
|
||||
if path.extension().is_none_or(|ext| ext != "toml") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(content) = fs::read_to_string(&path).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match toml::from_str::<StartConfig>(&content) {
|
||||
Ok(cfg) => {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
result.push(ConfigSummary {
|
||||
file_name,
|
||||
config_name: cfg
|
||||
.config_name
|
||||
.unwrap_or_else(|| entry.file_name().to_string_lossy().to_string()),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse configuration file {:?}: {:?}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.sort_by(|a, b| b.file_name.cmp(&a.file_name));
|
||||
Json(ApiResponse::success(result))
|
||||
}
|
||||
|
||||
async fn save_config(Json(req): Json<SaveConfigReq>) -> Json<ApiResponse<()>> {
|
||||
// 验证配置格式
|
||||
if let Err(e) = toml::from_str::<StartConfig>(&req.config) {
|
||||
log::warn!("Failed to parse configuration: {:?}", e);
|
||||
return Json(ApiResponse::error(format!("Invalid TOML format: {}", e)));
|
||||
}
|
||||
|
||||
let file_name = req
|
||||
.file_name
|
||||
.filter(|name| !name.is_empty())
|
||||
.unwrap_or_else(|| {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
format!("{}.toml", now)
|
||||
});
|
||||
|
||||
if !is_valid_file_name(&file_name) {
|
||||
return Json(ApiResponse::error("Invalid file name"));
|
||||
}
|
||||
|
||||
let target_path = Path::new(CONFIG_DIR).join(&file_name);
|
||||
|
||||
match fs::write(&target_path, &req.config).await {
|
||||
Ok(_) => Json(ApiResponse::success(())),
|
||||
Err(e) => Json(ApiResponse::error(format!("Write config failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_config(Query(req): Query<FileReq>) -> Json<ApiResponse<String>> {
|
||||
if !is_valid_file_name(&req.file_name) {
|
||||
return Json(ApiResponse::error("Invalid file name"));
|
||||
}
|
||||
|
||||
let path = Path::new(CONFIG_DIR).join(&req.file_name);
|
||||
|
||||
if !path.exists() {
|
||||
return Json(ApiResponse::error("Config file not found"));
|
||||
}
|
||||
|
||||
match fs::read_to_string(&path).await {
|
||||
Ok(content) => Json(ApiResponse::success(content)),
|
||||
Err(e) => Json(ApiResponse::error(format!("Read file failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_config(
|
||||
State(state): State<HttpAppState>,
|
||||
Query(req): Query<FileReq>,
|
||||
) -> Json<ApiResponse<()>> {
|
||||
if !is_valid_file_name(&req.file_name) {
|
||||
return Json(ApiResponse::error("Invalid file name"));
|
||||
}
|
||||
{
|
||||
if let Some(vnt) = &state.inner.lock().vnt
|
||||
&& vnt.config_file_name == req.file_name
|
||||
{
|
||||
return Json(ApiResponse::error("此配置已被使用,不能删除"));
|
||||
}
|
||||
}
|
||||
|
||||
let path = Path::new(CONFIG_DIR).join(&req.file_name);
|
||||
|
||||
if !path.exists() {
|
||||
return Json(ApiResponse::error("Config file not found"));
|
||||
}
|
||||
|
||||
match fs::remove_file(&path).await {
|
||||
Ok(_) => Json(ApiResponse::success(())),
|
||||
Err(e) => Json(ApiResponse::error(format!("Delete failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
|
||||
let server_addrs: Vec<ProtocolAddress> = cfg
|
||||
.server
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.parse()
|
||||
.map_err(|e| anyhow!("invalid server address '{}': {}", s, e))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let port_mapping: Vec<PortMapping> = cfg
|
||||
.port_mapping
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.parse()
|
||||
.map_err(|e| anyhow!("invalid port_mapping '{}': {}", s, e))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let cert_mode = match cfg.cert_mode.as_deref() {
|
||||
Some(s) => s
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("invalid cert_mode '{}': {}", s, e))?,
|
||||
None => CertValidationMode::InsecureSkipVerification,
|
||||
};
|
||||
|
||||
let device_id = match cfg.device_id {
|
||||
Some(id) => id,
|
||||
None => vnt_core::utils::device_id::get_device_id()
|
||||
.map_err(|e| anyhow!("failed to get device_id: {}", e))?,
|
||||
};
|
||||
|
||||
let device_name = cfg.device_name.unwrap_or_else(|| {
|
||||
hostname::get()
|
||||
.ok()
|
||||
.and_then(|v| v.into_string().ok())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let mut udp_stun = cfg.udp_stun;
|
||||
for x in udp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
let mut tcp_stun = cfg.tcp_stun;
|
||||
for x in tcp_stun.iter_mut() {
|
||||
if !x.contains(':') {
|
||||
x.push_str(":3478");
|
||||
}
|
||||
}
|
||||
Ok(CoreConfig {
|
||||
server_addr: server_addrs,
|
||||
network_code: cfg.network_code,
|
||||
ip: cfg.ip,
|
||||
no_punch: cfg.no_punch,
|
||||
rtx: cfg.rtx,
|
||||
compress: cfg.compress,
|
||||
device_id,
|
||||
device_name,
|
||||
tun_name: cfg.tun_name,
|
||||
password: cfg.password,
|
||||
cert_mode,
|
||||
input: cfg.input,
|
||||
output: cfg.output,
|
||||
no_nat: cfg.no_nat,
|
||||
no_tun: cfg.no_tun,
|
||||
mtu: cfg.mtu,
|
||||
port_mapping,
|
||||
allow_port_mapping: cfg.allow_mapping,
|
||||
udp_stun,
|
||||
tcp_stun,
|
||||
fec: cfg.fec,
|
||||
})
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
let ctrl_c = async {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install Ctrl+C handler");
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||
.expect("failed to install signal handler")
|
||||
.recv()
|
||||
.await;
|
||||
};
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let terminate = std::future::pending::<()>();
|
||||
|
||||
tokio::select! {
|
||||
_ = ctrl_c => {},
|
||||
_ = terminate => {},
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_peers(State(state): State<HttpAppState>) -> Json<ApiResponse<Vec<HttpClientItem>>> {
|
||||
let api = state.inner.lock().vnt.as_ref().map(|v| v.api.clone());
|
||||
|
||||
let Some(api) = api else {
|
||||
return Json(ApiResponse::error("VNT not running"));
|
||||
};
|
||||
|
||||
let key_sign = api.get_config().and_then(|config| config.key_sign());
|
||||
|
||||
let calc_key_equal = |peer_key_sign: &Option<String>| -> i32 {
|
||||
match (&key_sign, peer_key_sign) {
|
||||
(None, None) => 2,
|
||||
(Some(k1), Some(k2)) if k1 == k2 => 1,
|
||||
(Some(_), Some(_)) => 5,
|
||||
(Some(_), None) => 3,
|
||||
(None, Some(_)) => 4,
|
||||
}
|
||||
};
|
||||
|
||||
let build_nat_info = |ip: &Ipv4Addr| -> Option<HttpClientNatInfo> {
|
||||
api.peer_nat_info(ip).map(|v| HttpClientNatInfo {
|
||||
nat_type: format!("{:?}", v.nat_type),
|
||||
public_ips: v.public_ips,
|
||||
ipv6: v.ipv6,
|
||||
})
|
||||
};
|
||||
|
||||
let build_packet_loss = |ip: &Ipv4Addr| -> Option<HttpPacketLoss> {
|
||||
api.packet_loss_info(ip).map(|v| HttpPacketLoss {
|
||||
sent: v.sent,
|
||||
received: v.received,
|
||||
loss_rate: v.loss_rate,
|
||||
})
|
||||
};
|
||||
|
||||
let build_traffic = |ip: &Ipv4Addr| -> Option<HttpTraffic> {
|
||||
api.traffic_info(ip).map(|v| HttpTraffic {
|
||||
tx_bytes: v.tx_bytes,
|
||||
rx_bytes: v.rx_bytes,
|
||||
})
|
||||
};
|
||||
|
||||
let build_route = |ip: &Ipv4Addr| -> Option<HttpRouteDetail> {
|
||||
api.find_route(ip).map(|route| HttpRouteDetail {
|
||||
addr: route.route_key().to_string(),
|
||||
protocol: route.route_key().protocol().to_string(),
|
||||
metric: route.metric(),
|
||||
rtt: route.rtt(),
|
||||
})
|
||||
};
|
||||
|
||||
// 先从本地获取基础数据
|
||||
let mut merged: HashMap<Ipv4Addr, HttpClientItem> = api
|
||||
.client_ips()
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let ip = v.ip;
|
||||
let route = build_route(&ip);
|
||||
// 如果有路由,说明设备在线(可以直接通信)
|
||||
let has_route = route.is_some();
|
||||
(
|
||||
ip,
|
||||
HttpClientItem {
|
||||
ip,
|
||||
name: None,
|
||||
online: v.online || has_route,
|
||||
route,
|
||||
version: String::new(),
|
||||
last_connected_time: 0,
|
||||
key_equal: 0,
|
||||
nat_info: build_nat_info(&ip),
|
||||
packet_loss: build_packet_loss(&ip),
|
||||
traffic: build_traffic(&ip),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 从服务器获取更详细的信息
|
||||
if let Ok(resp) = api.server_rpc().client_list().await {
|
||||
for v in resp.list {
|
||||
let ip = Ipv4Addr::from(v.ip);
|
||||
let route = build_route(&ip);
|
||||
// 如果有路由,说明设备在线(可以直接通信)
|
||||
let has_route = route.is_some();
|
||||
merged.insert(
|
||||
ip,
|
||||
HttpClientItem {
|
||||
ip,
|
||||
name: Some(v.name),
|
||||
online: v.online || has_route,
|
||||
route,
|
||||
version: v.version,
|
||||
last_connected_time: v.last_connected_time,
|
||||
key_equal: calc_key_equal(&v.key_sign),
|
||||
nat_info: build_nat_info(&ip),
|
||||
packet_loss: build_packet_loss(&ip),
|
||||
traffic: build_traffic(&ip),
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Failed to get client list from server");
|
||||
}
|
||||
|
||||
let mut items: Vec<HttpClientItem> = merged.into_values().collect();
|
||||
items.sort_by_key(|it| it.ip);
|
||||
|
||||
Json(ApiResponse::success(items))
|
||||
}
|
||||
|
||||
async fn get_routes(State(state): State<HttpAppState>) -> Json<ApiResponse<Vec<HttpRouteItem>>> {
|
||||
let lock = state.inner.lock();
|
||||
|
||||
let Some(handler) = lock.vnt.as_ref() else {
|
||||
return Json(ApiResponse::error("VNT not running"));
|
||||
};
|
||||
|
||||
let table = handler.api.route_table();
|
||||
let items: Vec<HttpRouteItem> = table
|
||||
.into_iter()
|
||||
.map(|(ip, route_list)| HttpRouteItem {
|
||||
ip,
|
||||
routes: route_list
|
||||
.into_iter()
|
||||
.map(|v| HttpRouteDetail {
|
||||
addr: v.route_key().to_string(),
|
||||
protocol: v.route_key().protocol().to_string(),
|
||||
metric: v.metric(),
|
||||
rtt: v.rtt(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Json(ApiResponse::success(items))
|
||||
}
|
||||
@@ -0,0 +1,2884 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>VNT Dashboard</title>
|
||||
<link rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect x='0' y='0' width='64' height='64' rx='16' fill='%230f172a'/%3E%3Cpath d='M16 20 L32 48 L48 20' fill='none' stroke='%233b82f6' stroke-width='6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='20' r='6' fill='%2360a5fa'/%3E%3Ccircle cx='48' cy='20' r='6' fill='%2360a5fa'/%3E%3Ccircle cx='32' cy='48' r='6' fill='%2322c55e'/%3E%3C/svg%3E"
|
||||
type="image/svg+xml">
|
||||
<script src="tailwindcss3.4.17.js.gz"></script>
|
||||
<script src="vue.global.prod.js.gz"></script>
|
||||
<script src="vue-router.global.prod.js.gz"></script>
|
||||
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(30, 41, 59, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.input-dark {
|
||||
background-color: #1e293b;
|
||||
border-color: #334155;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.input-dark:focus {
|
||||
border-color: #3b82f6;
|
||||
outline: none;
|
||||
ring: 2px;
|
||||
}
|
||||
|
||||
/* Tooltip CSS (保留用于表格内的静态 tooltip) */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-text {
|
||||
visibility: hidden;
|
||||
width: 140px;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 6px 4px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
border: 1px solid #475569;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-text::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.9) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltip-text {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 路由切换动画 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(71, 85, 105, 0.8);
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(100, 116, 139, 0.9);
|
||||
}
|
||||
|
||||
/* Firefox 滚动条样式 */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(71, 85, 105, 0.8) rgba(15, 23, 42, 0.5);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-screen overflow-hidden flex flex-col font-sans">
|
||||
<div id="app" v-cloak class="flex h-full">
|
||||
<!-- 侧边栏 -->
|
||||
<aside
|
||||
class="w-64 bg-slate-900 border-r border-slate-800 flex flex-col shrink-0"
|
||||
>
|
||||
<div
|
||||
class="p-6 flex items-center justify-center border-b border-slate-800"
|
||||
>
|
||||
<h1 class="text-2xl font-bold text-blue-500 tracking-wider">
|
||||
VNT<span class="text-slate-400 text-sm ml-2">Web</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 p-4 space-y-2">
|
||||
<!-- 使用 router-link 和 v-slot 自定义渲染按钮 -->
|
||||
<router-link
|
||||
to="/general"
|
||||
custom
|
||||
v-slot="{ navigate, isActive }"
|
||||
>
|
||||
<button
|
||||
@click="navigate"
|
||||
:class="navClass(isActive)"
|
||||
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
></path>
|
||||
</svg>
|
||||
通用
|
||||
</button>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/config"
|
||||
custom
|
||||
v-slot="{ navigate, isActive }"
|
||||
>
|
||||
<button
|
||||
@click="navigate"
|
||||
:class="navClass(isActive)"
|
||||
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
></path>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
</svg>
|
||||
配置
|
||||
</button>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/peers"
|
||||
custom
|
||||
v-slot="{ navigate, isActive }"
|
||||
>
|
||||
<button
|
||||
@click="navigate"
|
||||
:class="navClass(isActive)"
|
||||
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
></path>
|
||||
</svg>
|
||||
设备列表
|
||||
</button>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/routes"
|
||||
custom
|
||||
v-slot="{ navigate, isActive }"
|
||||
>
|
||||
<button
|
||||
@click="navigate"
|
||||
:class="navClass(isActive)"
|
||||
class="w-full flex items-center p-3 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 01-.806-.98l-3.747-1.874O12 7m3 13V7m-3 0l3 3"
|
||||
></path>
|
||||
</svg>
|
||||
路由
|
||||
</button>
|
||||
</router-link>
|
||||
</nav>
|
||||
|
||||
<div class="p-4 text-xs text-slate-500 text-center">
|
||||
Client: v{{ info.version }}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main
|
||||
class="flex-1 flex flex-col bg-slate-900/50 relative overflow-hidden"
|
||||
>
|
||||
<!-- Header -->
|
||||
<header
|
||||
class="h-16 glass-panel border-b border-slate-700 flex items-center justify-between px-8 z-10 shrink-0"
|
||||
>
|
||||
<div class="flex items-center space-x-6">
|
||||
<div
|
||||
class="flex items-center space-x-2 bg-slate-800 rounded-full px-3 py-1 border border-slate-700"
|
||||
>
|
||||
<span
|
||||
class="w-2.5 h-2.5 rounded-full"
|
||||
:class="info.status === 'running' ? 'bg-green-500 animate-pulse' : (info.status === 'starting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')"
|
||||
></span>
|
||||
<span class="text-sm font-medium"
|
||||
>{{ info.status === 'running' ? '已运行' :
|
||||
(info.status === 'starting' ? '启动中...' :
|
||||
'未启动') }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="info.status === 'running'"
|
||||
class="flex items-center space-x-2 bg-slate-800 rounded-full px-3 py-1 border border-slate-700"
|
||||
:title="serverStatusText"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
:class="isServerConnected ? 'text-green-400' : 'text-red-400'"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||
></path>
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium"
|
||||
:class="isServerConnected ? 'text-green-400' : 'text-red-400'"
|
||||
>服务器: {{ isServerConnected ? '已连接' :
|
||||
'未连接' }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="info.ip"
|
||||
class="flex items-center space-x-2 text-slate-300"
|
||||
>
|
||||
<span
|
||||
class="font-mono text-blue-400 font-bold bg-blue-900/20 px-2 py-0.5 rounded"
|
||||
>{{ info.ip }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2 text-slate-400">
|
||||
<span class="text-sm">设备:</span>
|
||||
<span class="font-bold text-white"
|
||||
>{{ info.name || '' }}</span
|
||||
>
|
||||
<span
|
||||
class="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-500"
|
||||
title="Device ID"
|
||||
>{{ info.device_id.substring(0, 8) }}...</span
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Router View with Transition -->
|
||||
<div class="flex-1 overflow-auto scrollbar-hide p-8 relative">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component"/>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<!-- 启动日志弹窗 (Global) -->
|
||||
<div
|
||||
v-if="showStartLog"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
||||
>
|
||||
<div
|
||||
class="bg-slate-900/95 border border-slate-700 rounded-xl w-full max-w-2xl flex flex-col shadow-2xl overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="px-6 py-4 border-b border-slate-700 flex justify-between items-center bg-slate-800/50"
|
||||
>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div
|
||||
v-if="startStatus === 'starting'"
|
||||
class="w-3 h-3 bg-blue-500 rounded-full animate-ping"
|
||||
></div>
|
||||
<div
|
||||
v-else-if="startStatus === 'running'"
|
||||
class="w-3 h-3 bg-green-500 rounded-full"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="w-3 h-3 bg-red-500 rounded-full"
|
||||
></div>
|
||||
<h3 class="text-lg font-bold text-white">
|
||||
{{ startStatus === 'starting' ?
|
||||
'正在启动组网...' : (startStatus ===
|
||||
'running' ? '启动成功' : '启动失败') }}
|
||||
</h3>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs font-mono text-slate-500 uppercase tracking-widest"
|
||||
>{{ startStatus }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
ref="logContainer"
|
||||
class="flex-1 p-6 h-80 overflow-y-auto scrollbar-hide font-mono text-sm space-y-2 bg-black/20"
|
||||
>
|
||||
<div
|
||||
v-for="(log, idx) in startLogs"
|
||||
:key="idx"
|
||||
class="flex space-x-3 animate-in fade-in slide-in-from-left-2"
|
||||
>
|
||||
<span class="text-blue-500 shrink-0">>>></span>
|
||||
<span class="text-slate-300 break-all"
|
||||
>{{ log }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="startStatus === 'starting'"
|
||||
class="text-blue-400 animate-pulse italic mt-4"
|
||||
>
|
||||
等待后续步骤...
|
||||
</div>
|
||||
<div
|
||||
v-if="startStatus === 'stopped' && startLogs.length > 0"
|
||||
class="p-3 bg-red-900/20 border border-red-900/50 rounded-lg text-red-400 mt-4"
|
||||
>
|
||||
<strong>启动失败:</strong>
|
||||
请检查配置或网络连接。
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 border-t border-slate-700 flex justify-end space-x-3 bg-slate-800/50"
|
||||
>
|
||||
<button
|
||||
v-if="startStatus === 'starting'"
|
||||
@click="cancelStart"
|
||||
class="px-6 py-2 bg-slate-700 hover:bg-red-600 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
取消组网
|
||||
</button>
|
||||
<button
|
||||
v-if="startStatus === 'stopped' || startStatus === 'running'"
|
||||
@click="showStartLog = false"
|
||||
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
关闭窗口
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip (Global Teleport) -->
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="tooltipState.show"
|
||||
:style="{ top: tooltipState.y + 'px', left: tooltipState.x + 'px' }"
|
||||
class="fixed z-[9999] transform -translate-x-1/2 mt-1"
|
||||
@mouseenter="onTooltipEnter"
|
||||
@mouseleave="onTooltipLeave"
|
||||
>
|
||||
<div
|
||||
class="w-auto min-w-[260px] max-w-[320px] text-left p-4 bg-slate-800 border border-slate-600 shadow-2xl rounded-lg text-sm text-slate-200"
|
||||
>
|
||||
<div
|
||||
class="absolute -top-2 left-1/2 -translate-x-1/2 w-4 h-4 bg-slate-800 border-t border-l border-slate-600 transform rotate-45"
|
||||
></div>
|
||||
<div
|
||||
class="flex justify-between items-center border-b border-slate-600 pb-2 mb-2 relative z-10"
|
||||
>
|
||||
<span
|
||||
class="text-slate-400 text-xs uppercase font-bold"
|
||||
>NAT Type</span
|
||||
>
|
||||
<span
|
||||
class="text-green-400 font-bold bg-green-900/30 px-2 py-0.5 rounded text-xs border border-green-800"
|
||||
>{{ tooltipState.info.nat_type }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="tooltipState.info.public_ips && tooltipState.info.public_ips.length > 0"
|
||||
class="mb-3 relative z-10"
|
||||
>
|
||||
<span class="text-slate-400 text-xs block mb-1"
|
||||
>Public IPv4:</span
|
||||
>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="pip in tooltipState.info.public_ips"
|
||||
:key="pip"
|
||||
class="text-xs bg-slate-700 text-slate-200 px-1.5 py-0.5 rounded border border-slate-600"
|
||||
>{{ pip }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="tooltipState.info.ipv6"
|
||||
class="relative z-10"
|
||||
>
|
||||
<span class="text-slate-400 text-xs block mb-1"
|
||||
>IPv6:</span
|
||||
>
|
||||
<div
|
||||
class="text-slate-200 text-xs break-all whitespace-normal leading-relaxed bg-slate-900/50 p-1.5 rounded border border-slate-700/50"
|
||||
>
|
||||
{{ tooltipState.info.ipv6 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- ================= TEMPLATES ================= -->
|
||||
|
||||
<!-- 1. General (通用) -->
|
||||
<template id="tpl-general">
|
||||
<div class="space-y-6 max-w-5xl mx-auto">
|
||||
<div class="glass-panel rounded-xl p-6 shadow-lg">
|
||||
<h2
|
||||
class="text-xl font-bold mb-6 text-white border-l-4 border-blue-500 pl-3"
|
||||
>
|
||||
运行控制
|
||||
</h2>
|
||||
<div class="flex items-end space-x-4">
|
||||
<div class="flex-1">
|
||||
<label
|
||||
class="block text-sm font-medium text-slate-400 mb-2"
|
||||
>选择配置</label
|
||||
>
|
||||
<select
|
||||
v-model="localSelectedConfig"
|
||||
:disabled="info.status === 'starting'"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-4 py-2.5 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<option value="" disabled>请选择配置...</option>
|
||||
<option
|
||||
v-for="cfg in configList"
|
||||
:key="cfg.file_name"
|
||||
:value="cfg.file_name"
|
||||
>
|
||||
{{ cfg.config_name || cfg.file_name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
v-if="info.status === 'running'"
|
||||
@click="handleRestart"
|
||||
:disabled="loading || !localSelectedConfig"
|
||||
class="px-6 py-2.5 rounded-lg font-bold text-white shadow-lg transition-transform active:scale-95 flex items-center disabled:opacity-50 disabled:cursor-not-allowed bg-blue-500 hover:bg-blue-600"
|
||||
>
|
||||
<span v-if="loading" class="mr-2 animate-spin">⟳</span>
|
||||
重启
|
||||
</button>
|
||||
<button
|
||||
@click="handleToggle"
|
||||
:disabled="loading || (!localSelectedConfig && info.status !== 'running' && info.status !== 'starting')"
|
||||
:class="(info.status === 'running' || info.status === 'starting') ? 'bg-red-500 hover:bg-red-600' : 'bg-green-500 hover:bg-green-600'"
|
||||
class="px-8 py-2.5 rounded-lg font-bold text-white shadow-lg transition-transform active:scale-95 flex items-center disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span v-if="loading" class="mr-2 animate-spin"
|
||||
>⟳</span
|
||||
>
|
||||
{{ (info.status === 'running' || info.status ===
|
||||
'starting') ? '停止运行' : '启动运行' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div
|
||||
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-blue-500"
|
||||
>
|
||||
<span class="text-slate-400 text-sm mb-1"
|
||||
>在线设备</span
|
||||
>
|
||||
<span class="text-3xl font-bold text-blue-400"
|
||||
>{{ info.online_client_num }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-green-500"
|
||||
>
|
||||
<span class="text-slate-400 text-sm mb-1"
|
||||
>直连设备</span
|
||||
>
|
||||
<span class="text-3xl font-bold text-green-400"
|
||||
>{{ info.direct_client_num }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-slate-500"
|
||||
>
|
||||
<span class="text-slate-400 text-sm mb-1"
|
||||
>离线设备</span
|
||||
>
|
||||
<span class="text-3xl font-bold text-slate-400"
|
||||
>{{ info.offline_client_num }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="glass-panel p-5 rounded-xl flex flex-col items-center justify-center border-t-4 border-t-yellow-500"
|
||||
>
|
||||
<span class="text-slate-400 text-sm mb-1"
|
||||
>当前配置</span
|
||||
>
|
||||
<span
|
||||
class="text-lg font-medium text-yellow-400 truncate w-full text-center px-2"
|
||||
:title="info.current_config_file"
|
||||
>
|
||||
{{ info.current_config_name || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-panel rounded-xl p-6 shadow-lg">
|
||||
<h2 class="text-lg font-bold mb-4 text-white">网络详情</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">虚拟 IP / 掩码</span>
|
||||
<span class="font-mono text-white"
|
||||
>{{ info.ip || '-' }} / {{ info.prefix_len ||
|
||||
'-' }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">网关</span>
|
||||
<span class="font-mono text-white"
|
||||
>{{ info.gateway || '-' }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">网络编号</span>
|
||||
<span class="font-mono text-white"
|
||||
>{{ info.network_code || '-' }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">MTU</span>
|
||||
<span class="font-mono text-white"
|
||||
>{{ info.mtu || '' }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">NAT 类型</span>
|
||||
<span class="font-mono text-blue-300"
|
||||
>{{ info.nat_type || 'Unknown' }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-slate-700 pb-2"
|
||||
>
|
||||
<span class="text-slate-400">Public IPv6</span>
|
||||
<span
|
||||
class="font-mono text-white truncate max-w-[200px]"
|
||||
:title="info.public_ipv6"
|
||||
>{{ info.public_ipv6 || '-' }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="info.encrypt ? 'bg-green-500' : 'bg-slate-600'"></span>
|
||||
<span class="text-sm text-slate-300">加密</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="info.compress ? 'bg-green-500' : 'bg-slate-600'"></span>
|
||||
<span class="text-sm text-slate-300">压缩</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="info.fec ? 'bg-green-500' : 'bg-slate-600'"></span>
|
||||
<span class="text-sm text-slate-300">FEC纠错</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 bg-slate-800/50 rounded-lg px-3 py-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="info.rtx ? 'bg-green-500' : 'bg-slate-600'"></span>
|
||||
<span class="text-sm text-slate-300">QUIC传输</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<span class="text-slate-400 text-sm block mb-2"
|
||||
>Public IPv4s</span
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="pip in info.public_ipv4s"
|
||||
:key="pip"
|
||||
class="px-2 py-1 bg-slate-800 rounded text-xs font-mono text-green-300 border border-slate-600"
|
||||
>{{ pip }}</span
|
||||
>
|
||||
<span
|
||||
v-if="!info.public_ipv4s || info.public_ipv4s.length === 0"
|
||||
class="text-slate-600 text-xs"
|
||||
>无</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-panel rounded-xl p-6 shadow-lg">
|
||||
<h2 class="text-lg font-bold mb-4 text-white">
|
||||
服务器连接列表
|
||||
</h2>
|
||||
<div
|
||||
class="overflow-auto max-h-[400px] custom-scrollbar rounded-lg border border-slate-700"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-slate-700">
|
||||
<thead class="bg-slate-800">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
地址
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
状态
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
延迟
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
版本
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-700 bg-slate-900/30"
|
||||
>
|
||||
<tr
|
||||
v-for="(server, idx) in info.server_info"
|
||||
:key="idx"
|
||||
>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-300 font-mono"
|
||||
>
|
||||
{{ server.server }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
:class="server.connected ? 'bg-green-900 text-green-300' : 'bg-red-900 text-red-300'"
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
|
||||
>
|
||||
{{ server.connected ? '已连接' :
|
||||
'未连接' }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
|
||||
>
|
||||
{{ server.server_rtt ? server.server_rtt
|
||||
+ ' ms' : '-' }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
|
||||
>
|
||||
{{ server.server_version || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="!info.server_info || info.server_info.length === 0"
|
||||
>
|
||||
<td
|
||||
colspan="4"
|
||||
class="px-6 py-4 text-center text-slate-500"
|
||||
>
|
||||
暂无数据
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 2. Config (配置) -->
|
||||
<template id="tpl-config">
|
||||
<div class="space-y-6 max-w-5xl mx-auto h-full flex flex-col">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-2xl font-bold text-white">配置管理</h2>
|
||||
<button
|
||||
@click="openEditor(null)"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium flex items-center transition-colors"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
></path>
|
||||
</svg>
|
||||
新建配置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
<div
|
||||
v-for="cfg in configList"
|
||||
:key="cfg.file_name"
|
||||
:class="{'ring-2 ring-green-500 bg-slate-800/80': info.current_config_file === cfg.file_name, 'bg-slate-800/40 hover:bg-slate-800/60': info.current_config_file !== cfg.file_name}"
|
||||
class="rounded-xl p-5 border border-slate-700 transition-all cursor-pointer group relative overflow-hidden flex flex-col justify-between min-h-[140px]"
|
||||
@click="openEditor(cfg.file_name)"
|
||||
>
|
||||
<div
|
||||
v-if="info.current_config_file === cfg.file_name"
|
||||
class="absolute top-0 right-0 bg-green-500 text-white text-xs px-2 py-1 rounded-bl"
|
||||
>
|
||||
Running
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<div
|
||||
class="p-2 rounded bg-blue-500/10 text-blue-400 mr-3 mt-1"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<h3
|
||||
class="font-bold text-lg text-white truncate"
|
||||
:title="cfg.config_name"
|
||||
>
|
||||
{{ cfg.config_name || 'Unnamed' }}
|
||||
</h3>
|
||||
<p
|
||||
class="text-xs text-slate-500 font-mono truncate"
|
||||
:title="cfg.file_name"
|
||||
>
|
||||
{{ cfg.file_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-4 flex justify-end opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<button
|
||||
@click.stop="openEditor(cfg.file_name)"
|
||||
class="text-blue-400 hover:text-blue-300 text-sm mr-4"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
@click.stop="deleteConfig(cfg.file_name)"
|
||||
class="text-red-400 hover:text-red-300 text-sm"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑器 Modal -->
|
||||
<div
|
||||
v-if="showEditor"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
|
||||
>
|
||||
<div
|
||||
class="bg-slate-900 border border-slate-700 rounded-xl w-full max-w-6xl h-[85vh] flex flex-col shadow-2xl"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center p-4 border-b border-slate-700 bg-slate-800/50">
|
||||
<h3 class="text-lg font-bold text-white">
|
||||
{{ editorMode === 'new' ? '新建配置' : '编辑配置' }}
|
||||
</h3>
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 模式切换按钮 -->
|
||||
<div class="flex bg-slate-800 rounded-lg p-1 border border-slate-600">
|
||||
<button
|
||||
@click="switchToFormMode"
|
||||
:class="editMode === 'form' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'"
|
||||
class="px-4 py-1.5 rounded text-sm font-medium transition-colors flex items-center"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
表单模式
|
||||
</button>
|
||||
<button
|
||||
@click="switchToTomlMode"
|
||||
:class="editMode === 'toml' ? 'bg-blue-600 text-white' : 'text-slate-400 hover:text-white'"
|
||||
class="px-4 py-1.5 rounded text-sm font-medium transition-colors flex items-center"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
</svg>
|
||||
TOML模式
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-slate-500 font-mono" v-if="editorFileName">
|
||||
{{ editorFileName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<!-- 表单模式 -->
|
||||
<div v-show="editMode === 'form'" class="h-full overflow-y-auto scrollbar-hide p-6">
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<!-- 基础配置 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-blue-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
基础配置
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">配置名称</label>
|
||||
<input v-model="formData.config_name" type="text" placeholder="例如: 我的VPN配置"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
网络编号 <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input v-model="formData.network_code" type="text" placeholder="例如: my_network" required
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
服务器地址 <span class="text-red-400">*</span>
|
||||
<span class="text-xs text-slate-500 ml-2">支持 quic:// tcp:// wss:// dynamic://</span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(server, idx) in formData.server" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.server[idx]" type="text" placeholder="例如: quic://1.2.3.4:29872"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<button @click="formData.server.splice(idx, 1)" v-if="formData.server.length > 1"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.server.push('')"
|
||||
class="w-full px-3 py-2 bg-blue-600/20 hover:bg-blue-600/40 text-blue-400 rounded-lg transition-colors text-sm flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
添加服务器
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络设置 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-green-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path>
|
||||
</svg>
|
||||
网络设置
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
自定义虚拟IP
|
||||
<span class="text-xs text-slate-500 ml-1">(可选)</span>
|
||||
</label>
|
||||
<input v-model="formData.ip" type="text" placeholder="例如: 10.26.0.2"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">MTU</label>
|
||||
<input v-model.number="formData.mtu" type="number" placeholder="1380"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 传输优化 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-purple-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
传输优化
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">QUIC传输优化</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">重传丢包</div>
|
||||
</div>
|
||||
<input v-model="formData.rtx" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">FEC前向纠错</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">损失部分带宽提升稳定性</div>
|
||||
</div>
|
||||
<input v-model="formData.fec" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">LZ4压缩</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">减少传输数据量</div>
|
||||
</div>
|
||||
<input v-model="formData.compress" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">关闭P2P打洞</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">仅通过服务器中转</div>
|
||||
</div>
|
||||
<input v-model="formData.no_punch" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安全配置 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-red-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
</svg>
|
||||
安全配置
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">组网加密密码(连接公共服务器时建议填写,同一组网密码需要相同)</label>
|
||||
<input v-model="formData.password" type="password" placeholder="留空则不加密"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">服务端证书校验模式</label>
|
||||
<select v-model="formData.cert_mode"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<option value="skip">跳过验证 (默认)</option>
|
||||
<option value="standard">系统证书验证</option>
|
||||
<option value="finger">证书指纹验证</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="formData.cert_mode === 'finger'" class="animate-in fade-in slide-in-from-top-2">
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
证书指纹
|
||||
<span class="text-xs text-slate-500 ml-1">(服务端启动时日志会输出指纹)</span>
|
||||
</label>
|
||||
<input v-model="formData.fingerprint" type="text" placeholder="例如: 3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NAT与路由 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-yellow-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 9m0 0V7m0 2v6"></path>
|
||||
</svg>
|
||||
NAT与路由 (点对网)
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
入栈网段
|
||||
<span class="text-xs text-slate-500 ml-1">格式: CIDR,目标IP</span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, idx) in formData.input" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.input[idx]" type="text" placeholder="例如: 192.168.0.0/24,10.26.0.2"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
|
||||
<button @click="formData.input.splice(idx, 1)"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.input.push('')"
|
||||
class="w-full px-3 py-1.5 bg-yellow-600/20 hover:bg-yellow-600/40 text-yellow-400 rounded-lg transition-colors text-sm">
|
||||
+ 添加入栈网段
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
出栈网段
|
||||
<span class="text-xs text-slate-500 ml-1">格式: CIDR (允许转发的网段)</span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, idx) in formData.output" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.output[idx]" type="text" placeholder="例如: 0.0.0.0/0"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
|
||||
<button @click="formData.output.splice(idx, 1)"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.output.push('')"
|
||||
class="w-full px-3 py-1.5 bg-yellow-600/20 hover:bg-yellow-600/40 text-yellow-400 rounded-lg transition-colors text-sm">
|
||||
+ 添加出栈网段
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">关闭内置NAT</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">使用系统网卡转发</div>
|
||||
</div>
|
||||
<input v-model="formData.no_nat" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">关闭TUN网卡</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">仅作流量出口或端口映射</div>
|
||||
</div>
|
||||
<input v-model="formData.no_tun" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 端口映射 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-orange-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
端口映射
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
||||
映射规则
|
||||
<span class="text-xs text-slate-500 ml-1">格式: 协议://监听地址-虚拟IP-目标地址</span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, idx) in formData.port_mapping" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.port_mapping[idx]" type="text" placeholder="例如: tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm font-mono">
|
||||
<button @click="formData.port_mapping.splice(idx, 1)"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.port_mapping.push('')"
|
||||
class="w-full px-3 py-1.5 bg-orange-600/20 hover:bg-orange-600/40 text-orange-400 rounded-lg transition-colors text-sm">
|
||||
+ 添加映射规则
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="flex items-center justify-between p-3 bg-slate-800/50 rounded-lg border border-slate-700 cursor-pointer hover:bg-slate-800 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-white">允许作为映射出口</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">允许其他设备使用本机作跳板</div>
|
||||
</div>
|
||||
<input v-model="formData.allow_mapping" type="checkbox" class="w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-blue-500">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备配置 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-cyan-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
设备配置
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">设备名称</label>
|
||||
<input v-model="formData.device_name" type="text" placeholder="默认为主机名"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">设备ID</label>
|
||||
<input v-model="formData.device_id" type="text" placeholder="自动生成"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">虚拟网卡名</label>
|
||||
<input v-model="formData.tun_name" type="text" placeholder="默认为vnt-tun"
|
||||
class="w-full bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STUN配置 -->
|
||||
<div class="glass-panel rounded-lg p-5 border border-slate-700">
|
||||
<h4 class="text-md font-bold text-pink-400 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
STUN配置 (高级)
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">UDP STUN服务器</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, idx) in formData.udp_stun" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.udp_stun[idx]" type="text" placeholder="例如: stun.l.google.com:19302"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
|
||||
<button @click="formData.udp_stun.splice(idx, 1)"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.udp_stun.push('')"
|
||||
class="w-full px-3 py-1.5 bg-pink-600/20 hover:bg-pink-600/40 text-pink-400 rounded-lg transition-colors text-sm">
|
||||
+ 添加UDP STUN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-2">TCP STUN服务器</label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(item, idx) in formData.tcp_stun" :key="idx" class="flex space-x-2">
|
||||
<input v-model="formData.tcp_stun[idx]" type="text" placeholder="例如: stun.nextcloud.com:443"
|
||||
class="flex-1 bg-slate-800 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
|
||||
<button @click="formData.tcp_stun.splice(idx, 1)"
|
||||
class="px-3 py-2 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="formData.tcp_stun.push('')"
|
||||
class="w-full px-3 py-1.5 bg-pink-600/20 hover:bg-pink-600/40 text-pink-400 rounded-lg transition-colors text-sm">
|
||||
+ 添加TCP STUN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOML模式 -->
|
||||
<div v-show="editMode === 'toml'" class="h-full">
|
||||
<textarea
|
||||
v-model="editorContent"
|
||||
class="w-full h-full bg-[#1e1e1e] text-[#d4d4d4] font-mono p-4 resize-none focus:outline-none text-sm"
|
||||
spellcheck="false"
|
||||
placeholder="# 在此处输入 TOML 配置..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="p-4 border-t border-slate-700 flex justify-between items-center bg-slate-800/50">
|
||||
<div class="text-xs text-slate-500">
|
||||
<span v-if="editMode === 'form'">填写完成后保存即可生成配置文件</span>
|
||||
<span v-else>* 请使用标准 TOML 格式</span>
|
||||
</div>
|
||||
<div class="space-x-3">
|
||||
<button
|
||||
@click="showEditor = false"
|
||||
class="px-4 py-2 rounded text-slate-300 hover:text-white transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
@click="saveConfig"
|
||||
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded font-medium shadow-lg transition-colors"
|
||||
>
|
||||
保存配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 3. Peers (设备列表) -->
|
||||
<template id="tpl-peers">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="glass-panel rounded-xl shadow-lg overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-slate-700 flex justify-between items-center"
|
||||
>
|
||||
<h2 class="text-xl font-bold text-white">设备列表</h2>
|
||||
<div class="flex space-x-4 text-sm text-slate-400">
|
||||
<span
|
||||
>Online:
|
||||
<span class="text-white"
|
||||
>{{ peers.filter(p => p.online).length
|
||||
}}</span
|
||||
></span
|
||||
>
|
||||
<span
|
||||
>Total:
|
||||
<span class="text-white"
|
||||
>{{ peers.length }}</span
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto max-h-[600px] custom-scrollbar">
|
||||
<table class="min-w-full divide-y divide-slate-700">
|
||||
<thead class="bg-slate-800">
|
||||
<tr>
|
||||
<th class="w-8 px-2 py-3"></th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
IP地址
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
名称
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
版本
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
状态
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
模式
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
延迟
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
丢包率
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
流量
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 tracking-wider"
|
||||
>
|
||||
最后在线
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-700 bg-slate-900/30"
|
||||
>
|
||||
<template v-for="peer in peers" :key="peer.ip">
|
||||
<tr class="hover:bg-slate-800/50 transition-colors">
|
||||
<td class="px-2 py-4 text-center cursor-pointer select-none" @click="toggleExpand(peer.ip)">
|
||||
<svg class="w-4 h-4 text-slate-500 transition-transform duration-200 inline-block" :class="{ 'rotate-90': expandedPeers[peer.ip] }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-blue-300"
|
||||
>
|
||||
<span
|
||||
class="border-b border-dotted border-blue-500/50 pb-0.5 cursor-help"
|
||||
@mouseenter="showPeerTooltip($event, peer)"
|
||||
@mouseleave="hidePeerTooltip"
|
||||
>
|
||||
{{ peer.ip }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-300"
|
||||
>
|
||||
{{ peer.name || '-' }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-xs text-slate-500"
|
||||
>
|
||||
{{ peer.version || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
:class="peer.online ? 'bg-green-900 text-green-300' : 'bg-slate-700 text-slate-400'"
|
||||
class="px-2 py-0.5 text-xs rounded-full font-medium"
|
||||
>{{ peer.online ? 'Online' : 'Offline' }}</span>
|
||||
<!-- 加密状态图标 -->
|
||||
<div v-if="peer.online && peer.key_equal === 1" class="tooltip">
|
||||
<svg
|
||||
class="w-4 h-4 text-green-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="tooltip-text">双方加密传输</span>
|
||||
</div>
|
||||
<div v-else-if="peer.online && peer.key_equal === 2" class="tooltip">
|
||||
<svg
|
||||
class="w-4 h-4 text-yellow-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="tooltip-text">双方未加密</span>
|
||||
</div>
|
||||
<div v-else-if="peer.online && [3,4,5].includes(peer.key_equal)" class="tooltip cursor-help group">
|
||||
<svg
|
||||
class="w-4 h-4 text-red-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="tooltip-text">{{ peer.key_equal === 3 ? '己方加密对方未加密' : peer.key_equal === 4 ? '己方未加密对方加密' : peer.key_equal === 5 ? '密钥不一致' : '未知错误' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm"
|
||||
>
|
||||
<span
|
||||
v-if="peer.online && peer.route"
|
||||
:class="getRouteModeClass(peer.route)"
|
||||
>{{ getRouteModeText(peer.route) }}</span
|
||||
>
|
||||
<span v-else-if="peer.online" class="text-yellow-400">服务器中继</span>
|
||||
<span v-else class="text-slate-600"
|
||||
>-</span
|
||||
>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
|
||||
>
|
||||
{{ peer.route ? peer.route.rtt + ' ms' : '-' }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm"
|
||||
>
|
||||
<span v-if="peer.packet_loss"
|
||||
:class="peer.packet_loss.loss_rate > 10 ? 'text-red-400' : peer.packet_loss.loss_rate > 5 ? 'text-yellow-400' : 'text-green-400'"
|
||||
:title="'Sent: ' + peer.packet_loss.sent + ', Received: ' + peer.packet_loss.received"
|
||||
>{{ peer.packet_loss.loss_rate.toFixed(1) }}%</span>
|
||||
<span v-else class="text-slate-600">-</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-xs"
|
||||
>
|
||||
<div v-if="peer.traffic" class="leading-relaxed">
|
||||
<div class="text-green-400">↑ {{ formatBytes(peer.traffic.tx_bytes) }} ({{ formatSpeed(peer.traffic.tx_speed) }})</div>
|
||||
<div class="text-blue-400">↓ {{ formatBytes(peer.traffic.rx_bytes) }} ({{ formatSpeed(peer.traffic.rx_speed) }})</div>
|
||||
</div>
|
||||
<span v-else class="text-slate-600">-</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 font-mono text-xs"
|
||||
>
|
||||
{{ formatTime(peer.last_connected_time)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="expandedPeers[peer.ip]">
|
||||
<td :colspan="10" class="p-0">
|
||||
<div class="px-4 py-3 bg-slate-950/60 border-t border-slate-700/50">
|
||||
<div class="flex items-center space-x-4 mb-2 text-xs text-slate-400">
|
||||
<span class="flex items-center"><span class="inline-block w-3 h-0.5 bg-green-400 mr-1"></span>上传速度</span>
|
||||
<span class="flex items-center"><span class="inline-block w-3 h-0.5 bg-blue-400 mr-1"></span>下载速度</span>
|
||||
<span class="ml-auto" :id="'chart-max-' + peer.ip.replaceAll('.', '-')"></span>
|
||||
</div>
|
||||
<canvas :id="'chart-' + peer.ip.replaceAll('.', '-')" style="width:100%;height:150px;display:block;" class="rounded"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 4. Routes (路由) -->
|
||||
<template id="tpl-routes">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="glass-panel rounded-xl shadow-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-slate-700">
|
||||
<h2 class="text-xl font-bold text-white">路由表</h2>
|
||||
</div>
|
||||
<div class="overflow-auto max-h-[600px] custom-scrollbar">
|
||||
<table class="min-w-full divide-y divide-slate-700">
|
||||
<thead class="bg-slate-800">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
目标节点IP
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
目标网络
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
跳数
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-slate-400 uppercase tracking-wider"
|
||||
>
|
||||
延迟
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-700 bg-slate-900/30"
|
||||
>
|
||||
<template v-for="item in routes" :key="item.ip">
|
||||
<tr
|
||||
v-for="(route, rIdx) in item.routes"
|
||||
:key="rIdx"
|
||||
class="hover:bg-slate-800/50"
|
||||
>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-blue-300"
|
||||
v-if="rIdx===0"
|
||||
:rowspan="item.routes.length"
|
||||
>
|
||||
{{ item.ip }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap font-mono text-sm text-yellow-300"
|
||||
>
|
||||
{{ route.addr }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
|
||||
>
|
||||
{{ route.metric }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-slate-400"
|
||||
>
|
||||
{{ route.rtt }} ms
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const {
|
||||
createApp,
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
computed,
|
||||
watch,
|
||||
nextTick,
|
||||
inject,
|
||||
provide,
|
||||
} = Vue;
|
||||
const {createRouter, createWebHashHistory} = VueRouter;
|
||||
|
||||
const API_BASE = "";
|
||||
|
||||
// 格式化时间工具
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return "-";
|
||||
const date = new Date(timestamp * 1000);
|
||||
const pad = (n) => (n < 10 ? "0" + n : n);
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
};
|
||||
|
||||
// 格式化字节数
|
||||
const formatBytes = (bytes) => {
|
||||
if (bytes === 0 || bytes === undefined || bytes === null) return "0B";
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
let i = 0;
|
||||
let value = bytes;
|
||||
while (value >= 1024 && i < units.length - 1) {
|
||||
value /= 1024;
|
||||
i++;
|
||||
}
|
||||
return i === 0 ? value + units[i] : value.toFixed(2) + units[i];
|
||||
};
|
||||
|
||||
// 格式化速度(字节/秒)
|
||||
const formatSpeed = (bytesPerSecond) => {
|
||||
if (bytesPerSecond === 0 || bytesPerSecond === undefined || bytesPerSecond === null) return "0B/s";
|
||||
const units = ["B/s", "KB/s", "MB/s", "GB/s"];
|
||||
let i = 0;
|
||||
let value = bytesPerSecond;
|
||||
while (value >= 1024 && i < units.length - 1) {
|
||||
value /= 1024;
|
||||
i++;
|
||||
}
|
||||
return i === 0 ? value + units[i] : value.toFixed(2) + units[i];
|
||||
};
|
||||
|
||||
// --- 组件定义 ---
|
||||
|
||||
const GeneralView = {
|
||||
template: "#tpl-general",
|
||||
setup() {
|
||||
const info = inject("info");
|
||||
const configList = inject("configList");
|
||||
const toggleVnt = inject("toggleVnt");
|
||||
const restartVnt = inject("restartVnt");
|
||||
const loading = inject("loading");
|
||||
const localSelectedConfig = ref("");
|
||||
|
||||
// 同步当前配置
|
||||
watch(
|
||||
() => info.value.current_config_file,
|
||||
(newVal) => {
|
||||
if (
|
||||
(info.value.status === "running" ||
|
||||
info.value.status === "starting") &&
|
||||
newVal
|
||||
) {
|
||||
localSelectedConfig.value = newVal;
|
||||
}
|
||||
},
|
||||
{immediate: true},
|
||||
);
|
||||
|
||||
const handleToggle = () => {
|
||||
// 如果是启动,且有本地选择的配置,传递给 toggle
|
||||
if (
|
||||
info.value.status !== "running" &&
|
||||
info.value.status !== "starting"
|
||||
) {
|
||||
toggleVnt(localSelectedConfig.value);
|
||||
} else {
|
||||
toggleVnt(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestart = () => {
|
||||
if (localSelectedConfig.value) {
|
||||
restartVnt(localSelectedConfig.value);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
info,
|
||||
configList,
|
||||
localSelectedConfig,
|
||||
loading,
|
||||
handleToggle,
|
||||
handleRestart,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const ConfigView = {
|
||||
template: "#tpl-config",
|
||||
setup() {
|
||||
const info = inject("info");
|
||||
const configList = inject("configList");
|
||||
const fetchConfigList = inject("fetchConfigList");
|
||||
|
||||
// 编辑器状态
|
||||
const showEditor = ref(false);
|
||||
const editorContent = ref("");
|
||||
const editorFileName = ref("");
|
||||
const editorMode = ref("new");
|
||||
const editMode = ref("form"); // 'form' 或 'toml'
|
||||
const originalToml = ref(""); // 保存原始TOML内容(包含用户注释)
|
||||
const hasTomlChanges = ref(false); // 标记TOML是否被修改过
|
||||
const hasFormChanges = ref(false); // 标记表单是否被修改过
|
||||
const isParsingToml = ref(false); // 标记是否正在解析TOML(用于避免触发watch)
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
config_name: "",
|
||||
network_code: "",
|
||||
server: [""],
|
||||
ip: "",
|
||||
mtu: null,
|
||||
rtx: false,
|
||||
fec: false,
|
||||
compress: false,
|
||||
no_punch: false,
|
||||
input: [],
|
||||
output: [],
|
||||
no_nat: false,
|
||||
no_tun: false,
|
||||
port_mapping: [],
|
||||
allow_mapping: false,
|
||||
device_name: "",
|
||||
device_id: "",
|
||||
tun_name: "",
|
||||
password: "",
|
||||
cert_mode: "skip",
|
||||
fingerprint: "",
|
||||
udp_stun: [],
|
||||
tcp_stun: []
|
||||
});
|
||||
|
||||
// 从TOML解析到表单
|
||||
const parseTomlToForm = (toml) => {
|
||||
const data = {
|
||||
config_name: "",
|
||||
network_code: "",
|
||||
server: [""],
|
||||
ip: "",
|
||||
mtu: null,
|
||||
rtx: false,
|
||||
fec: false,
|
||||
compress: false,
|
||||
no_punch: false,
|
||||
input: [],
|
||||
output: [],
|
||||
no_nat: false,
|
||||
no_tun: false,
|
||||
port_mapping: [],
|
||||
allow_mapping: false,
|
||||
device_name: "",
|
||||
device_id: "",
|
||||
tun_name: "",
|
||||
password: "",
|
||||
cert_mode: "skip",
|
||||
fingerprint: "",
|
||||
udp_stun: [],
|
||||
tcp_stun: []
|
||||
};
|
||||
|
||||
const lines = toml.split('\n');
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
|
||||
if (trimmed.includes('config_name')) {
|
||||
const match = trimmed.match(/config_name\s*=\s*"([^"]*)"/);
|
||||
if (match) data.config_name = match[1];
|
||||
} else if (trimmed.includes('network_code')) {
|
||||
const match = trimmed.match(/network_code\s*=\s*"([^"]*)"/);
|
||||
if (match) data.network_code = match[1];
|
||||
} else if (trimmed.startsWith('server')) {
|
||||
const match = trimmed.match(/server\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.server = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
} else if (trimmed.includes('ip =')) {
|
||||
const match = trimmed.match(/ip\s*=\s*"([^"]*)"/);
|
||||
if (match) data.ip = match[1];
|
||||
} else if (trimmed.includes('mtu =')) {
|
||||
const match = trimmed.match(/mtu\s*=\s*(\d+)/);
|
||||
if (match) data.mtu = parseInt(match[1]);
|
||||
} else if (trimmed.match(/^rtx\s*=/)) {
|
||||
data.rtx = trimmed.includes('true');
|
||||
} else if (trimmed.match(/^fec\s*=/)) {
|
||||
data.fec = trimmed.includes('true');
|
||||
} else if (trimmed.match(/^compress\s*=/)) {
|
||||
data.compress = trimmed.includes('true');
|
||||
} else if (trimmed.match(/^no_punch\s*=/)) {
|
||||
data.no_punch = trimmed.includes('true');
|
||||
} else if (trimmed.startsWith('input')) {
|
||||
const match = trimmed.match(/input\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.input = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
} else if (trimmed.startsWith('output')) {
|
||||
const match = trimmed.match(/output\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.output = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
} else if (trimmed.match(/^no_nat\s*=/)) {
|
||||
data.no_nat = trimmed.includes('true');
|
||||
} else if (trimmed.match(/^no_tun\s*=/)) {
|
||||
data.no_tun = trimmed.includes('true');
|
||||
} else if (trimmed.startsWith('port_mapping')) {
|
||||
const match = trimmed.match(/port_mapping\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.port_mapping = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
} else if (trimmed.match(/^allow_mapping\s*=/)) {
|
||||
data.allow_mapping = trimmed.includes('true');
|
||||
} else if (trimmed.includes('device_name')) {
|
||||
const match = trimmed.match(/device_name\s*=\s*"([^"]*)"/);
|
||||
if (match) data.device_name = match[1];
|
||||
} else if (trimmed.includes('device_id')) {
|
||||
const match = trimmed.match(/device_id\s*=\s*"([^"]*)"/);
|
||||
if (match) data.device_id = match[1];
|
||||
} else if (trimmed.includes('tun_name')) {
|
||||
const match = trimmed.match(/tun_name\s*=\s*"([^"]*)"/);
|
||||
if (match) data.tun_name = match[1];
|
||||
} else if (trimmed.includes('password =')) {
|
||||
const match = trimmed.match(/password\s*=\s*"([^"]*)"/);
|
||||
if (match) data.password = match[1];
|
||||
} else if (trimmed.includes('cert_mode')) {
|
||||
const match = trimmed.match(/cert_mode\s*=\s*"([^"]*)"/);
|
||||
if (match) {
|
||||
const value = match[1];
|
||||
if (value.startsWith('finger:')) {
|
||||
data.cert_mode = 'finger';
|
||||
data.fingerprint = value.substring(7); // 去掉 "finger:" 前缀
|
||||
} else {
|
||||
data.cert_mode = value;
|
||||
}
|
||||
}
|
||||
} else if (trimmed.startsWith('udp_stun')) {
|
||||
const match = trimmed.match(/udp_stun\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.udp_stun = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
} else if (trimmed.startsWith('tcp_stun')) {
|
||||
const match = trimmed.match(/tcp_stun\s*=\s*\[(.*)\]/);
|
||||
if (match) {
|
||||
const items = match[1].match(/"([^"]*)"/g);
|
||||
if (items) data.tcp_stun = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// 从表单生成TOML
|
||||
const formToToml = () => {
|
||||
let toml = '';
|
||||
|
||||
if (formData.value.config_name) {
|
||||
toml += `# 配置名称\nconfig_name = "${formData.value.config_name}"\n`;
|
||||
}
|
||||
|
||||
toml += '\n# --- 网络配置 ---\n';
|
||||
toml += '# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)\n';
|
||||
toml += `network_code = "${formData.value.network_code}"\n\n`;
|
||||
|
||||
const servers = formData.value.server.filter(s => s.trim());
|
||||
if (servers.length > 0) {
|
||||
toml += '# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)\n';
|
||||
toml += '# dynamic 协议使用dns txt解析记录值\n';
|
||||
toml += `server = [${servers.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
if (formData.value.ip) {
|
||||
toml += '\n# 自定义虚拟 IP (可选)\n';
|
||||
toml += `ip = "${formData.value.ip}"\n`;
|
||||
}
|
||||
|
||||
if (formData.value.rtx) {
|
||||
toml += '\n# 是否启用quic优化传输 (默认 false)\n';
|
||||
toml += '# 开启后传输过程几乎不会丢包,但是延迟可能会有波动\n';
|
||||
toml += 'rtx = true\n';
|
||||
}
|
||||
|
||||
if (formData.value.fec) {
|
||||
toml += '\n# 是否启用 FEC 前向纠错 (默认 false)\n';
|
||||
toml += '# 开启后可以减少丢包率,损失带宽但是延迟比较稳定,带宽充足时可以使用此功能\n';
|
||||
toml += 'fec = true\n';
|
||||
}
|
||||
|
||||
if (formData.value.no_punch) {
|
||||
toml += '\n# 是否关闭 P2P 打洞 (默认 false)\n';
|
||||
toml += 'no_punch = true\n';
|
||||
}
|
||||
|
||||
if (formData.value.compress) {
|
||||
toml += '\n# 是否启用 LZ4 压缩 (默认 false)\n';
|
||||
toml += 'compress = true\n';
|
||||
}
|
||||
|
||||
const inputs = formData.value.input.filter(s => s.trim());
|
||||
if (inputs.length > 0) {
|
||||
toml += '\n# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点\n';
|
||||
toml += '# 例如192.168.0.0/24,10.26.0.2 表示将192.168.0.0/24网段的数据转发到10.26.0.2\n';
|
||||
toml += `input = [${inputs.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
const outputs = formData.value.output.filter(s => s.trim());
|
||||
if (outputs.length > 0) {
|
||||
toml += '\n# 出栈允许网段,用于点对网,允许指定网段的转发\n';
|
||||
toml += `output = [${outputs.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
if (formData.value.no_nat) {
|
||||
toml += '\n# 是否关闭内置子网NAT,关闭后需要配置网卡转发,否则无法使用点对网\n';
|
||||
toml += '# 通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好\n';
|
||||
toml += 'no_nat = true\n';
|
||||
}
|
||||
|
||||
if (formData.value.no_tun) {
|
||||
toml += '\n# 是否关闭TUN虚拟网卡,关闭后只能充当流量出口或者进行端口映射,关闭后无需管理员权限\n';
|
||||
toml += 'no_tun = true\n';
|
||||
}
|
||||
|
||||
const portMappings = formData.value.port_mapping.filter(s => s.trim());
|
||||
if (portMappings.length > 0) {
|
||||
toml += '\n# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址\n';
|
||||
toml += '# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址\n';
|
||||
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 表示将本地tcp的81端口的数据转发到10.0.0.2:80\n';
|
||||
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80\n';
|
||||
toml += '# 例如: tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80\n';
|
||||
toml += `port_mapping = [${portMappings.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
if (formData.value.allow_mapping) {
|
||||
toml += '\n# 是否允许作为端口映射出口,开启后其他设备才可使用本设备的ip为"目标虚拟IP"\n';
|
||||
toml += '# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络\n';
|
||||
toml += 'allow_mapping = true\n';
|
||||
}
|
||||
|
||||
if (formData.value.mtu) {
|
||||
toml += '\n# MTU 设置\n';
|
||||
toml += `mtu = ${formData.value.mtu}\n`;
|
||||
}
|
||||
|
||||
toml += '\n# --- 设备配置 ---\n';
|
||||
if (formData.value.device_name) {
|
||||
toml += '\n# 设备名称 (可选,默认读取本机 hostname)\n';
|
||||
toml += `device_name = "${formData.value.device_name}"\n`;
|
||||
}
|
||||
if (formData.value.device_id) {
|
||||
toml += '\n# 设备 ID (可选,不填自动生成,不同设备ID不能相同)\n';
|
||||
toml += `device_id = "${formData.value.device_id}"\n`;
|
||||
}
|
||||
if (formData.value.tun_name) {
|
||||
toml += '\n# 虚拟网卡名称\n';
|
||||
toml += `tun_name = "${formData.value.tun_name}"\n`;
|
||||
}
|
||||
|
||||
toml += '\n# --- 安全配置 ---\n';
|
||||
if (formData.value.password) {
|
||||
toml += '\n# 组网加密密码 (可选)\n';
|
||||
toml += `password = "${formData.value.password}"\n`;
|
||||
}
|
||||
if (formData.value.cert_mode && formData.value.cert_mode !== 'skip') {
|
||||
toml += '\n# 证书校验方式:\n';
|
||||
toml += '# skip 跳过验证(默认)\n';
|
||||
toml += '# standard 使用系统证书验证\n';
|
||||
toml += '# finger 使用证书指纹验证,服务端启动时日志会输出指纹\n';
|
||||
if (formData.value.cert_mode === 'finger' && formData.value.fingerprint) {
|
||||
toml += `cert_mode = "finger:${formData.value.fingerprint}"\n`;
|
||||
} else {
|
||||
toml += `cert_mode = "${formData.value.cert_mode}"\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const udpStuns = formData.value.udp_stun.filter(s => s.trim());
|
||||
if (udpStuns.length > 0) {
|
||||
toml += '\n# 自定义UDP STUN地址,不设置则用默认stun\n';
|
||||
toml += `udp_stun = [${udpStuns.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
const tcpStuns = formData.value.tcp_stun.filter(s => s.trim());
|
||||
if (tcpStuns.length > 0) {
|
||||
toml += '\n# 自定义TCP STUN地址,不设置则用默认stun\n';
|
||||
toml += `tcp_stun = [${tcpStuns.map(s => `"${s}"`).join(', ')}]\n`;
|
||||
}
|
||||
|
||||
return toml;
|
||||
};
|
||||
|
||||
// 切换到表单模式
|
||||
const switchToFormMode = () => {
|
||||
if (editMode.value === 'toml') {
|
||||
editMode.value = 'form';
|
||||
// 从TOML解析到表单
|
||||
isParsingToml.value = true;
|
||||
formData.value = parseTomlToForm(editorContent.value);
|
||||
nextTick(() => {
|
||||
isParsingToml.value = false;
|
||||
});
|
||||
} else {
|
||||
editMode.value = 'form';
|
||||
}
|
||||
};
|
||||
|
||||
// 切换到TOML模式
|
||||
const switchToTomlMode = () => {
|
||||
if (editMode.value === 'form') {
|
||||
// 如果表单被修改过,生成新的TOML
|
||||
if (hasFormChanges.value) {
|
||||
editorContent.value = formToToml();
|
||||
// 重置标记,因为表单修改已经应用到TOML了
|
||||
hasFormChanges.value = false;
|
||||
} else if (originalToml.value && !hasTomlChanges.value) {
|
||||
// 如果表单没被修改,且TOML也没被修改,使用原始TOML(保留用户注释)
|
||||
editorContent.value = originalToml.value;
|
||||
} else {
|
||||
// 其他情况生成新的TOML
|
||||
editorContent.value = formToToml();
|
||||
}
|
||||
}
|
||||
editMode.value = 'toml';
|
||||
};
|
||||
|
||||
// 监听TOML内容变化(只在TOML模式下)
|
||||
watch(editorContent, (newVal, oldVal) => {
|
||||
if (editMode.value === 'toml' && oldVal !== undefined) {
|
||||
hasTomlChanges.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听表单数据变化
|
||||
watch(formData, () => {
|
||||
if (editMode.value === 'form' && showEditor.value && !isParsingToml.value) {
|
||||
hasFormChanges.value = true;
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
const openEditor = async (fileName) => {
|
||||
editorFileName.value = fileName || "";
|
||||
editorMode.value = fileName ? "edit" : "new";
|
||||
editMode.value = "form"; // 默认表单模式
|
||||
hasTomlChanges.value = false; // 重置TOML修改标记
|
||||
hasFormChanges.value = false; // 重置表单修改标记
|
||||
|
||||
if (fileName) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/config?file_name=${fileName}`,
|
||||
);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) {
|
||||
editorContent.value = json.data;
|
||||
originalToml.value = json.data; // 保存原始TOML
|
||||
isParsingToml.value = true;
|
||||
formData.value = parseTomlToForm(json.data);
|
||||
nextTick(() => {
|
||||
isParsingToml.value = false;
|
||||
});
|
||||
showEditor.value = true;
|
||||
} else alert("获取配置失败: " + json.msg);
|
||||
} catch (e) {
|
||||
alert("网络错误");
|
||||
}
|
||||
} else {
|
||||
// 新建配置,初始化表单
|
||||
originalToml.value = ""; // 新建时清空原始TOML
|
||||
formData.value = {
|
||||
config_name: "",
|
||||
network_code: "",
|
||||
server: [""],
|
||||
ip: "",
|
||||
mtu: null,
|
||||
rtx: false,
|
||||
fec: false,
|
||||
compress: false,
|
||||
no_punch: false,
|
||||
input: [],
|
||||
output: [],
|
||||
no_nat: false,
|
||||
no_tun: false,
|
||||
port_mapping: [],
|
||||
allow_mapping: false,
|
||||
device_name: "",
|
||||
device_id: "",
|
||||
tun_name: "",
|
||||
password: "",
|
||||
cert_mode: "skip",
|
||||
fingerprint: "",
|
||||
udp_stun: [],
|
||||
tcp_stun: []
|
||||
};
|
||||
editorContent.value = `# config_name = "配置名称"
|
||||
# --- 网络配置 ---
|
||||
# 网络编号,相同网络编号的会组在同一个虚拟网 (必填)
|
||||
network_code = "your_network_code"
|
||||
|
||||
# 服务器地址列表(支持 quic / tcp / wss / dynamic) (必填)
|
||||
# dynamic 协议使用dns txt解析记录值
|
||||
server = ["quic://1.2.3.4:29872"]
|
||||
|
||||
# ===简单使用以下参数可以不动===
|
||||
|
||||
# 自定义虚拟 IP (可选)
|
||||
# ip = "10.10.0.2"
|
||||
|
||||
# 是否启用quic优化传输 (默认 false,设置为true时开启)
|
||||
# 开启后传输过程几乎不会丢包,但是延迟会有波动
|
||||
# rtx = false
|
||||
|
||||
# 是否启用 FEC 前向纠错,损失一定带宽来提升网络稳定性(默认 false,设置为true时开启)
|
||||
# 开启后可以减少丢包率,损失带宽但是延迟比较稳定,带宽充足时可以使用此功能
|
||||
# fec = false
|
||||
|
||||
# 是否关闭 P2P 打洞 (默认 false,设置为true时关闭)
|
||||
# no_punch = false
|
||||
|
||||
# 是否启用 LZ4 压缩 (默认 false,设置为true时开启)
|
||||
# compress = false
|
||||
|
||||
# 入栈监听网段 (逗号分隔的 CIDR 和目标 IP),用于点对网,将指定网段的流量发送到目标节点
|
||||
# input = ["192.168.0.0/24,10.26.0.2", "192.168.1.0/24,10.26.0.3"]
|
||||
|
||||
# 出栈允许网段,用于点对网,允许指定网段的转发
|
||||
# output = ["0.0.0.0/0"]
|
||||
|
||||
# 是否关闭内置子网NAT,关闭(设为true)后需要配置网卡转发,否则无法使用点对网。通常关闭内置子网NAT,使用系统的网卡转发,点对网性能会更好
|
||||
# no_nat = false
|
||||
|
||||
# 是否关闭TUN虚拟网卡,关闭(设为true)后只能充当流量出口或者进行端口映射,关闭后无需管理员权限
|
||||
# no_tun = false
|
||||
|
||||
# 端口映射,格式为:协议://本地监听地址-目标虚拟IP-目标映射地址
|
||||
# 端口映射用于在本地监听指定端口,并将收到的网络流量经由指定虚拟节点转发到目标地址,从而实现跨网络或内网服务访问
|
||||
# 例如 port_mapping = ["tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80"]
|
||||
# tcp://0.0.0.0:81-10.0.0.2-10.0.0.2:80 则表示将本地tcp的81端口的数据转发到10.0.0.2:80
|
||||
# tcp://0.0.0.0:81-10.0.0.2-192.168.1.10:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到192.168.1.10:80
|
||||
# tcp://0.0.0.0:81-10.0.0.2-anyonehost:80 则表示将本地tcp的81端口的数据经过10.0.0.2转到anyonehost:80
|
||||
# port_mapping = []
|
||||
|
||||
# 是否允许作为端口映射出口,开启(设置为true)后其他设备才可使用本设备的ip为"目标虚拟IP"
|
||||
# 开启后虚拟网络其他设备可以使用此设备当跳板访问其他网络
|
||||
# allow_mapping = false
|
||||
|
||||
# MTU 设置
|
||||
# mtu = 1400
|
||||
|
||||
# --- 设备配置 ---
|
||||
|
||||
# 设备名称 (可选,默认读取本机 hostname)
|
||||
# device_name = "my-device"
|
||||
|
||||
# 设备 ID (可选,不填自动生成,不同设备ID不能相同)
|
||||
# device_id = "device-id-xxxx"
|
||||
|
||||
# 虚拟网卡名称
|
||||
# tun_name = "vnt-tun"
|
||||
|
||||
# --- 安全配置 ---
|
||||
|
||||
# 加密密码 (可选)
|
||||
# password = "123456"
|
||||
|
||||
# 证书校验方式:
|
||||
# skip 跳过验证(默认)
|
||||
# standard 使用系统证书验证
|
||||
# finger 使用证书指纹验证,服务端启动时日志会输出指纹,
|
||||
# 例如 finger:3bdd8675606837cdf95d5e13445606315762315a78555f9da652940a25feaec1
|
||||
# cert_mode = "skip"
|
||||
|
||||
# --- 其他配置 ---
|
||||
# 自定义stun地址,分别用于udp打洞和tcp打洞,需要单独配置,不设置则用默认stun
|
||||
# udp_stun = ["stun.chat.bilibili.com"]
|
||||
# tcp_stun = ["stun.nextcloud.com:443"]`;
|
||||
showEditor.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
// 如果是表单模式,先转换为TOML
|
||||
let configContent = editorContent.value;
|
||||
if (editMode.value === 'form') {
|
||||
// 验证必填项
|
||||
if (!formData.value.network_code.trim()) {
|
||||
alert('请填写网络编号');
|
||||
return;
|
||||
}
|
||||
const servers = formData.value.server.filter(s => s.trim());
|
||||
if (servers.length === 0) {
|
||||
alert('请至少填写一个服务器地址');
|
||||
return;
|
||||
}
|
||||
configContent = formToToml();
|
||||
}
|
||||
|
||||
const payload = {
|
||||
file_name: editorFileName.value || null,
|
||||
config: configContent,
|
||||
};
|
||||
const res = await fetch(`${API_BASE}/api/config`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.code === 0) {
|
||||
showEditor.value = false;
|
||||
fetchConfigList();
|
||||
} else alert("保存失败: " + json.msg);
|
||||
} catch (e) {
|
||||
alert("保存失败: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteConfig = async (fileName) => {
|
||||
if (!confirm(`确定要删除配置 ${fileName} 吗?`)) return;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/config?file_name=${fileName}`,
|
||||
{method: "DELETE"},
|
||||
);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) fetchConfigList();
|
||||
else alert(json.msg);
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
info,
|
||||
configList,
|
||||
showEditor,
|
||||
editorContent,
|
||||
editorFileName,
|
||||
editorMode,
|
||||
editMode,
|
||||
formData,
|
||||
openEditor,
|
||||
saveConfig,
|
||||
deleteConfig,
|
||||
switchToFormMode,
|
||||
switchToTomlMode,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const PeersView = {
|
||||
template: "#tpl-peers",
|
||||
setup() {
|
||||
const peers = ref([]);
|
||||
const info = inject("info");
|
||||
const isPageVisible = inject("isPageVisible");
|
||||
const showTooltipGlobal = inject("showPeerTooltip");
|
||||
const hideTooltipGlobal = inject("hidePeerTooltip");
|
||||
let timer = null;
|
||||
// 记录上次流量数据和时间,用于前端计算网速
|
||||
let lastTrafficMap = {};
|
||||
let lastFetchTime = 0;
|
||||
// 展开状态和网速历史
|
||||
const expandedPeers = reactive({});
|
||||
const speedHistoryMap = {};
|
||||
const HISTORY_SIZE = 60;
|
||||
|
||||
const toggleExpand = (ip) => {
|
||||
expandedPeers[ip] = !expandedPeers[ip];
|
||||
if (expandedPeers[ip]) {
|
||||
nextTick(() => drawChart(ip));
|
||||
}
|
||||
};
|
||||
|
||||
const drawChart = (ip) => {
|
||||
const canvasId = 'chart-' + ip.replaceAll('.', '-');
|
||||
const canvas = document.getElementById(canvasId);
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const history = speedHistoryMap[ip];
|
||||
const txArr = history ? history.tx : [];
|
||||
const rxArr = history ? history.rx : [];
|
||||
|
||||
// 高清适配
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * dpr;
|
||||
canvas.height = rect.height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
const w = rect.width;
|
||||
const h = rect.height;
|
||||
|
||||
const padTop = 8, padBottom = 4, padLeft = 0, padRight = 0;
|
||||
const chartW = w - padLeft - padRight;
|
||||
const chartH = h - padTop - padBottom;
|
||||
|
||||
// 背景
|
||||
ctx.fillStyle = '#0c1222';
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// 计算Y轴最大值
|
||||
const allValues = [...txArr, ...rxArr];
|
||||
let maxVal = allValues.length > 0 ? Math.max(...allValues) : 0;
|
||||
if (maxVal < 1024) maxVal = 1024; // 最小1KB
|
||||
// 向上取整到合适的刻度
|
||||
const niceMax = niceNumber(maxVal);
|
||||
|
||||
// 更新最大值标签
|
||||
const maxLabel = document.getElementById('chart-max-' + ip.replaceAll('.', '-'));
|
||||
if (maxLabel) maxLabel.textContent = '峰值: ' + formatSpeed(niceMax);
|
||||
|
||||
// 网格线
|
||||
const gridLines = 4;
|
||||
ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let i = 0; i <= gridLines; i++) {
|
||||
const y = padTop + (chartH / gridLines) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padLeft, y);
|
||||
ctx.lineTo(padLeft + chartW, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
// 垂直网格线
|
||||
const vLines = 6;
|
||||
for (let i = 0; i <= vLines; i++) {
|
||||
const x = padLeft + (chartW / vLines) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, padTop);
|
||||
ctx.lineTo(x, padTop + chartH);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 绘制曲线
|
||||
const drawLine = (data, strokeColor, fillColor) => {
|
||||
if (data.length < 2) return;
|
||||
const step = chartW / (HISTORY_SIZE - 1);
|
||||
const offset = HISTORY_SIZE - data.length;
|
||||
|
||||
// 填充区域
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padLeft + offset * step, padTop + chartH);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const x = padLeft + (offset + i) * step;
|
||||
const y = padTop + chartH - (data[i] / niceMax) * chartH;
|
||||
if (i === 0) ctx.lineTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo(padLeft + (offset + data.length - 1) * step, padTop + chartH);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fillColor;
|
||||
ctx.fill();
|
||||
|
||||
// 线条
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const x = padLeft + (offset + i) * step;
|
||||
const y = padTop + chartH - (data[i] / niceMax) * chartH;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.strokeStyle = strokeColor;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
drawLine(rxArr, '#60a5fa', 'rgba(96, 165, 250, 0.15)');
|
||||
drawLine(txArr, '#4ade80', 'rgba(74, 222, 128, 0.15)');
|
||||
};
|
||||
|
||||
// 将数值取整到适合的刻度
|
||||
const niceNumber = (val) => {
|
||||
const units = [
|
||||
1024, // 1KB
|
||||
10 * 1024, // 10KB
|
||||
100 * 1024, // 100KB
|
||||
1024 * 1024, // 1MB
|
||||
10 * 1024 * 1024, // 10MB
|
||||
100 * 1024 * 1024,// 100MB
|
||||
1024 * 1024 * 1024,// 1GB
|
||||
];
|
||||
for (const u of units) {
|
||||
if (val <= u) return u;
|
||||
}
|
||||
return Math.ceil(val / (1024 * 1024 * 1024)) * 1024 * 1024 * 1024;
|
||||
};
|
||||
|
||||
const fetchPeers = async () => {
|
||||
if (info.value.status !== "running") {
|
||||
peers.value = [];
|
||||
lastTrafficMap = {};
|
||||
lastFetchTime = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/peers`);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) {
|
||||
const now = Date.now();
|
||||
const list = json.data || [];
|
||||
const elapsed = lastFetchTime > 0 ? (now - lastFetchTime) / 1000 : 0;
|
||||
const newTrafficMap = {};
|
||||
for (const peer of list) {
|
||||
if (peer.traffic) {
|
||||
const key = peer.ip;
|
||||
const prev = lastTrafficMap[key];
|
||||
if (prev && elapsed > 0) {
|
||||
const txDiff = Math.max(0, peer.traffic.tx_bytes - prev.tx_bytes);
|
||||
const rxDiff = Math.max(0, peer.traffic.rx_bytes - prev.rx_bytes);
|
||||
peer.traffic.tx_speed = Math.round(txDiff / elapsed);
|
||||
peer.traffic.rx_speed = Math.round(rxDiff / elapsed);
|
||||
} else {
|
||||
peer.traffic.tx_speed = 0;
|
||||
peer.traffic.rx_speed = 0;
|
||||
}
|
||||
newTrafficMap[key] = { tx_bytes: peer.traffic.tx_bytes, rx_bytes: peer.traffic.rx_bytes };
|
||||
// 记录速度历史
|
||||
if (!speedHistoryMap[key]) speedHistoryMap[key] = { tx: [], rx: [] };
|
||||
speedHistoryMap[key].tx.push(peer.traffic.tx_speed);
|
||||
speedHistoryMap[key].rx.push(peer.traffic.rx_speed);
|
||||
if (speedHistoryMap[key].tx.length > HISTORY_SIZE) {
|
||||
speedHistoryMap[key].tx.shift();
|
||||
speedHistoryMap[key].rx.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTrafficMap = newTrafficMap;
|
||||
lastFetchTime = now;
|
||||
peers.value = list;
|
||||
// 重绘所有展开的图表
|
||||
nextTick(() => {
|
||||
for (const ip in expandedPeers) {
|
||||
if (expandedPeers[ip]) drawChart(ip);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchPeers();
|
||||
timer = setInterval(() => {
|
||||
if (isPageVisible.value) fetchPeers();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
// 监听 status 变化,当变为 running 时立即获取数据
|
||||
watch(() => info.value.status, (newStatus) => {
|
||||
if (newStatus === "running") {
|
||||
fetchPeers();
|
||||
}
|
||||
});
|
||||
|
||||
const getRouteModeClass = (route) => {
|
||||
// route 包含 addr, protocol, metric, rtt
|
||||
// 判断是否直连:metric === 1
|
||||
const isDirect = route.metric === 1;
|
||||
|
||||
if (isDirect) {
|
||||
return 'text-purple-400 font-medium'; // 直连 - 紫色
|
||||
} else {
|
||||
return 'text-blue-400'; // 客户端中继 - 蓝色
|
||||
}
|
||||
};
|
||||
|
||||
const getRouteModeText = (route) => {
|
||||
const isDirect = route.metric === 1;
|
||||
const isTcp = route.protocol.includes('Tcp');
|
||||
|
||||
if (isDirect) {
|
||||
return isTcp ? '打洞TCP直连' : '打洞UDP直连';
|
||||
} else {
|
||||
return isTcp ? '客户端TCP中继' : '客户端UDP中继';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
peers,
|
||||
expandedPeers,
|
||||
toggleExpand,
|
||||
formatTime,
|
||||
formatBytes,
|
||||
formatSpeed,
|
||||
getRouteModeClass,
|
||||
getRouteModeText,
|
||||
showPeerTooltip: showTooltipGlobal,
|
||||
hidePeerTooltip: hideTooltipGlobal,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const RoutesView = {
|
||||
template: "#tpl-routes",
|
||||
setup() {
|
||||
const routes = ref([]);
|
||||
const info = inject("info");
|
||||
const isPageVisible = inject("isPageVisible");
|
||||
let timer = null;
|
||||
|
||||
const fetchRoutes = async () => {
|
||||
if (info.value.status !== "running") {
|
||||
routes.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/routes`);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) routes.value = json.data || [];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchRoutes();
|
||||
timer = setInterval(() => {
|
||||
if (isPageVisible.value) fetchRoutes();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
|
||||
// 监听 status 变化,当变为 running 时立即获取数据
|
||||
watch(() => info.value.status, (newStatus) => {
|
||||
if (newStatus === "running") {
|
||||
fetchRoutes();
|
||||
}
|
||||
});
|
||||
|
||||
return {routes};
|
||||
},
|
||||
};
|
||||
|
||||
// --- 路由配置 ---
|
||||
|
||||
const routes = [
|
||||
{path: "/", redirect: "/general"},
|
||||
{path: "/general", component: GeneralView},
|
||||
{path: "/config", component: ConfigView},
|
||||
{path: "/peers", component: PeersView},
|
||||
{path: "/routes", component: RoutesView},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
// --- 主应用 ---
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
const info = ref({
|
||||
status: "stopped",
|
||||
ip: "",
|
||||
name: "",
|
||||
device_id: "",
|
||||
version: "",
|
||||
server_info: [],
|
||||
online_client_num: 0,
|
||||
direct_client_num: 0,
|
||||
offline_client_num: 0,
|
||||
current_config_file: null,
|
||||
current_config_name: null,
|
||||
});
|
||||
const configList = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 启动日志相关
|
||||
const showStartLog = ref(false);
|
||||
const startLogs = ref([]);
|
||||
const startStatus = ref("stopped");
|
||||
const logContainer = ref(null);
|
||||
let statusInterval = null;
|
||||
let infoTimer = null;
|
||||
|
||||
// Tooltip 相关
|
||||
const tooltipState = ref({
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
info: null,
|
||||
});
|
||||
let tooltipHideTimer = null;
|
||||
const showPeerTooltip = (event, peer) => {
|
||||
if (!peer.nat_info) return;
|
||||
if (tooltipHideTimer) {
|
||||
clearTimeout(tooltipHideTimer);
|
||||
tooltipHideTimer = null;
|
||||
}
|
||||
const rect =
|
||||
event.currentTarget.getBoundingClientRect();
|
||||
tooltipState.value = {
|
||||
show: true,
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.bottom + 10,
|
||||
info: peer.nat_info,
|
||||
};
|
||||
};
|
||||
const hidePeerTooltip = () => {
|
||||
tooltipHideTimer = setTimeout(() => {
|
||||
tooltipState.value.show = false;
|
||||
}, 100);
|
||||
};
|
||||
const onTooltipEnter = () => {
|
||||
if (tooltipHideTimer) {
|
||||
clearTimeout(tooltipHideTimer);
|
||||
tooltipHideTimer = null;
|
||||
}
|
||||
};
|
||||
const onTooltipLeave = () => {
|
||||
tooltipState.value.show = false;
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const isServerConnected = computed(
|
||||
() =>
|
||||
info.value.server_info &&
|
||||
info.value.server_info.some((s) => s.connected),
|
||||
);
|
||||
const serverStatusText = computed(() => {
|
||||
if (
|
||||
!info.value.server_info ||
|
||||
!info.value.server_info.length
|
||||
)
|
||||
return "未配置服务器";
|
||||
return `${info.value.server_info.filter((s) => s.connected).length} / ${info.value.server_info.length} 已连接`;
|
||||
});
|
||||
|
||||
// 基础 API
|
||||
const fetchInfo = async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/info`);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) info.value = json.data;
|
||||
} catch (e) {
|
||||
console.error("Fetch info error", e);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchConfigList = async () => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/config/list`,
|
||||
);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) configList.value = json.data;
|
||||
} catch (e) {
|
||||
console.error("Fetch list error", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 启动流程控制
|
||||
const pollStartStatus = async () => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/start/status`,
|
||||
);
|
||||
const json = await res.json();
|
||||
if (json.code === 0) {
|
||||
startLogs.value = json.data.logs || [];
|
||||
startStatus.value = json.data.status;
|
||||
nextTick(() => {
|
||||
if (logContainer.value)
|
||||
logContainer.value.scrollTop =
|
||||
logContainer.value.scrollHeight;
|
||||
});
|
||||
|
||||
if (startStatus.value === "running") {
|
||||
stopPolling();
|
||||
fetchInfo();
|
||||
showStartLog.value = false;
|
||||
} else if (
|
||||
startStatus.value === "stopped" &&
|
||||
startLogs.value.length > 0
|
||||
) {
|
||||
stopPolling();
|
||||
fetchInfo();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
if (statusInterval) {
|
||||
clearInterval(statusInterval);
|
||||
statusInterval = null;
|
||||
}
|
||||
};
|
||||
const startPolling = () => {
|
||||
stopPolling();
|
||||
statusInterval = setInterval(pollStartStatus, 1000);
|
||||
pollStartStatus();
|
||||
};
|
||||
|
||||
const openStartingModal = () => {
|
||||
startLogs.value = [];
|
||||
startStatus.value = "starting";
|
||||
showStartLog.value = true;
|
||||
startPolling();
|
||||
};
|
||||
|
||||
const toggleVnt = async (selectedFile) => {
|
||||
if (loading.value) return;
|
||||
|
||||
// 停止逻辑
|
||||
if (
|
||||
info.value.status === "running" ||
|
||||
info.value.status === "starting"
|
||||
) {
|
||||
loading.value = true;
|
||||
await fetch(`${API_BASE}/api/stop`, {
|
||||
method: "POST",
|
||||
});
|
||||
loading.value = false;
|
||||
stopPolling();
|
||||
showStartLog.value = false;
|
||||
fetchInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动逻辑
|
||||
if (!selectedFile) {
|
||||
alert("请先选择一个配置");
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/start`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({
|
||||
file_name: selectedFile,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
loading.value = false;
|
||||
if (json.code !== 0) {
|
||||
alert("启动失败: " + json.msg);
|
||||
return;
|
||||
}
|
||||
openStartingModal();
|
||||
} catch (e) {
|
||||
loading.value = false;
|
||||
alert("网络请求失败: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelStart = async () => {
|
||||
stopPolling();
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/stop`, {
|
||||
method: "POST",
|
||||
});
|
||||
startLogs.value.push("启动已手动取消");
|
||||
} catch (e) {
|
||||
}
|
||||
startStatus.value = "stopped";
|
||||
fetchInfo();
|
||||
};
|
||||
|
||||
const restartVnt = async (selectedFile) => {
|
||||
if (loading.value) return;
|
||||
if (!selectedFile) {
|
||||
alert("请先选择一个配置");
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/restart`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({
|
||||
file_name: selectedFile,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
loading.value = false;
|
||||
if (json.code !== 0) {
|
||||
alert("重启失败: " + json.msg);
|
||||
return;
|
||||
}
|
||||
openStartingModal();
|
||||
} catch (e) {
|
||||
loading.value = false;
|
||||
alert("网络请求失败: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 导航辅助
|
||||
const navClass = (isActive) =>
|
||||
isActive
|
||||
? "bg-blue-600 text-white shadow-lg shadow-blue-500/30"
|
||||
: "text-slate-400 hover:bg-slate-800 hover:text-white";
|
||||
|
||||
// 页面可见性处理
|
||||
const isPageVisible = ref(!document.hidden);
|
||||
const visibilityHandler = () => {
|
||||
isPageVisible.value = !document.hidden;
|
||||
};
|
||||
|
||||
// 依赖注入
|
||||
provide("info", info);
|
||||
provide("isPageVisible", isPageVisible);
|
||||
provide("configList", configList);
|
||||
provide("fetchConfigList", fetchConfigList);
|
||||
provide("toggleVnt", toggleVnt);
|
||||
provide("restartVnt", restartVnt);
|
||||
provide("loading", loading);
|
||||
provide("showPeerTooltip", showPeerTooltip);
|
||||
provide("hidePeerTooltip", hidePeerTooltip);
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener(
|
||||
"visibilitychange",
|
||||
visibilityHandler,
|
||||
);
|
||||
await fetchInfo();
|
||||
fetchConfigList();
|
||||
if (info.value.status === "starting")
|
||||
openStartingModal();
|
||||
|
||||
// 全局轮询 info (状态/IP等)
|
||||
infoTimer = setInterval(() => {
|
||||
if (info.value.status !== "running") return;
|
||||
if (isPageVisible.value) fetchInfo();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
visibilityHandler,
|
||||
);
|
||||
stopPolling();
|
||||
if (infoTimer) clearInterval(infoTimer);
|
||||
});
|
||||
|
||||
return {
|
||||
info,
|
||||
isServerConnected,
|
||||
serverStatusText,
|
||||
navClass,
|
||||
showStartLog,
|
||||
startStatus,
|
||||
startLogs,
|
||||
logContainer,
|
||||
cancelStart,
|
||||
tooltipState,
|
||||
onTooltipEnter,
|
||||
onTooltipLeave,
|
||||
};
|
||||
},
|
||||
})
|
||||
.use(router)
|
||||
.mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user