Compare commits
588 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198281b5eb | ||
|
|
426d711d0b | ||
|
|
73d3b60a2f | ||
|
|
b336b9ce03 | ||
|
|
9654efb952 | ||
|
|
c3262fc0cb | ||
|
|
2fef2c0eaa | ||
|
|
3d33cdc9f1 | ||
|
|
509ad47a71 | ||
|
|
c14a3b2773 | ||
|
|
e6e61b3cdb | ||
|
|
303587f91b | ||
|
|
5c3fd53fab | ||
|
|
0c675e6ff6 | ||
|
|
942b026a05 | ||
|
|
0b347b7f8d | ||
|
|
87322c335e | ||
|
|
1bf4b38268 | ||
|
|
60b1742088 | ||
|
|
266fa6f6fa | ||
|
|
02c3c9b374 | ||
|
|
8f7da3ed94 | ||
|
|
a5a6e8645a | ||
|
|
83022d3efc | ||
|
|
78143ce638 | ||
|
|
cee38766c5 | ||
|
|
c66147c686 | ||
|
|
08e149b180 | ||
|
|
e3f8b652f4 | ||
|
|
6a4a45c96b | ||
|
|
e5bf95bdc8 | ||
|
|
ed1e7253f3 | ||
|
|
db43daea0b | ||
|
|
0359ef40e1 | ||
|
|
89b5744e27 | ||
|
|
18f293a7aa | ||
|
|
acf70602ff | ||
|
|
ebba94b9d1 | ||
|
|
23c379faf9 | ||
|
|
124852b3a2 | ||
|
|
e6d557f61e | ||
|
|
0a2ed2e498 | ||
|
|
4ce5fd463d | ||
|
|
367cfb36dd | ||
|
|
57555ffc1e | ||
|
|
8a86a53bd2 | ||
|
|
4a7b1af383 | ||
|
|
78e631f551 | ||
|
|
0af83540d3 | ||
|
|
9004913483 | ||
|
|
44909ea6c6 | ||
|
|
a0cd66e319 | ||
|
|
8c4e5025ed | ||
|
|
442e7b7c01 | ||
|
|
05dfbe6f8a | ||
|
|
c64324227b | ||
|
|
1388f2e008 | ||
|
|
2752d79248 | ||
|
|
7aa24afcf2 | ||
|
|
b3622e709c | ||
|
|
02a58189ad | ||
|
|
f42184daaf | ||
|
|
7754fcc13e | ||
|
|
d421b79071 | ||
|
|
80146fbe0d | ||
|
|
e95a1f8ad5 | ||
|
|
9e496f246c | ||
|
|
f0389cdd5b | ||
|
|
bcca92affc | ||
|
|
421e72188b | ||
|
|
cc8416e6bf | ||
|
|
fb09a100e1 | ||
|
|
1b1091d75f | ||
|
|
ea30beb79b | ||
|
|
99881e2c70 | ||
|
|
325acb2942 | ||
|
|
6babe1ea10 | ||
|
|
6c586e2b78 | ||
|
|
6917ff3347 | ||
|
|
284bb83d64 | ||
|
|
1b29112a2c | ||
|
|
b9afc98230 | ||
|
|
20761d2183 | ||
|
|
1ab07c81ab | ||
|
|
07efc2c8de | ||
|
|
ef8737063b | ||
|
|
b5e0fa4895 | ||
|
|
02513a9449 | ||
|
|
0859452475 | ||
|
|
42d18ca1d3 | ||
|
|
08b3715bda | ||
|
|
253783573a | ||
|
|
56dc3fdc07 | ||
|
|
af20893551 | ||
|
|
85178223e0 | ||
|
|
e116bf8897 | ||
|
|
0fffedebd7 | ||
|
|
1e259b5c6f | ||
|
|
7bb5bb86dc | ||
|
|
18716cb3a2 | ||
|
|
1aca777c6d | ||
|
|
3a5b47b84a | ||
|
|
8bda30c4cf | ||
|
|
03ef384c48 | ||
|
|
5ace1eef1d | ||
|
|
d270b4a468 | ||
|
|
6154d4173a | ||
|
|
70067d67b9 | ||
|
|
8befdbc89c | ||
|
|
900b975c6a | ||
|
|
2ddb03a6c7 | ||
|
|
4145a31e5b | ||
|
|
96717f0c33 | ||
|
|
09348211b6 | ||
|
|
179f9d63ab | ||
|
|
7692446fd8 | ||
|
|
69f20e1d7a | ||
|
|
0bebee1537 | ||
|
|
61569c3a92 | ||
|
|
0d6e10ad33 | ||
|
|
77a6300dae | ||
|
|
8f268b8b56 | ||
|
|
efb5710727 | ||
|
|
7fc0b21764 | ||
|
|
2f524010a1 | ||
|
|
c537240d3d | ||
|
|
d3640f7519 | ||
|
|
6339e1ff53 | ||
|
|
55098e2d22 | ||
|
|
5a81229a9c | ||
|
|
7d59d9ce4d | ||
|
|
575326bed1 | ||
|
|
20ed5b1c7b | ||
|
|
997f438dfb | ||
|
|
e2f7377259 | ||
|
|
6fb3457bd2 | ||
|
|
462ae3deff | ||
|
|
44fd8b383b | ||
|
|
ade587db6d | ||
|
|
9a1f5de686 | ||
|
|
98479e860d | ||
|
|
6f71eef98f | ||
|
|
b76458722b | ||
|
|
003538cdaf | ||
|
|
c7c52a0788 | ||
|
|
167442e288 | ||
|
|
397c5e08f8 | ||
|
|
a993b1bb9d | ||
|
|
89be79b6c6 | ||
|
|
4d1c9ffa1f | ||
|
|
1d7eee0e69 | ||
|
|
bb0ddf3cec | ||
|
|
b3876d21be | ||
|
|
a26d7ea2c2 | ||
|
|
249ce1a6d3 | ||
|
|
3280e82da3 | ||
|
|
2aab40624f | ||
|
|
187a3f58dd | ||
|
|
0de6c174a2 | ||
|
|
f9a2c3cd75 | ||
|
|
a3eb0bfde9 | ||
|
|
33e665631d | ||
|
|
375eb9aa66 | ||
|
|
889ce76676 | ||
|
|
abdcae31c9 | ||
|
|
c9f8f6c6fb | ||
|
|
aab899397e | ||
|
|
a933322ef7 | ||
|
|
b9f054c495 | ||
|
|
5c2e6fd109 | ||
|
|
e427263ccf | ||
|
|
a1a2c3f985 | ||
|
|
84458a10e4 | ||
|
|
9dd7bf80c5 | ||
|
|
317d0113fb | ||
|
|
53153da2e6 | ||
|
|
747a94a1c8 | ||
|
|
df94ca5601 | ||
|
|
3cf7788f34 | ||
|
|
af91921070 | ||
|
|
7fc5c30f60 | ||
|
|
c4a7f0fbb0 | ||
|
|
f656773858 | ||
|
|
e208ccee18 | ||
|
|
5980fee788 | ||
|
|
61f791e77d | ||
|
|
907dec42e0 | ||
|
|
8dce99fec6 | ||
|
|
db13b83f44 | ||
|
|
dd8d7dd0d4 | ||
|
|
dd355b5d98 | ||
|
|
1e1999dede | ||
|
|
bb933cfb6e | ||
|
|
2603802dd9 | ||
|
|
68ad904772 | ||
|
|
af19e5d25b | ||
|
|
62907efe0c | ||
|
|
29e4cdf4e2 | ||
|
|
3a4a9a3a27 | ||
|
|
04ef338807 | ||
|
|
3c070a7da3 | ||
|
|
c34f7db239 | ||
|
|
1f655808c6 | ||
|
|
0226d2cde3 | ||
|
|
61bb2d8ca0 | ||
|
|
f5d09b878b | ||
|
|
6b4ee97f05 | ||
|
|
12dd591c58 | ||
|
|
3b49f17e01 | ||
|
|
cb8d0c0b42 | ||
|
|
e92375f6a9 | ||
|
|
c20e19d74f | ||
|
|
50886bd69a | ||
|
|
eaf836eff3 | ||
|
|
bf72325fc0 | ||
|
|
846956a9fe | ||
|
|
20c31b0c68 | ||
|
|
d84a4bec86 | ||
|
|
05d2f16777 | ||
|
|
9fa1c4932b | ||
|
|
0932aecff3 | ||
|
|
a9f8acd98d | ||
|
|
2336e77ac4 | ||
|
|
1ef8d7c82a | ||
|
|
24d54725df | ||
|
|
9b430b5ba4 | ||
|
|
0ee7abbd6a | ||
|
|
361215fb6f | ||
|
|
7c0daad40a | ||
|
|
726240e669 | ||
|
|
1449b94dd4 | ||
|
|
ee9cd7b181 | ||
|
|
ec0492b4b6 | ||
|
|
7b4cb4df04 | ||
|
|
7a31291c4c | ||
|
|
16f1d80588 | ||
|
|
7101530b22 | ||
|
|
7f0bea952d | ||
|
|
380c203222 | ||
|
|
c4ac760603 | ||
|
|
09158ebaa2 | ||
|
|
4709ccfca4 | ||
|
|
bea9ca25cf | ||
|
|
8ca1bfe09c | ||
|
|
878b5e8c8a | ||
|
|
1e9cfccd5a | ||
|
|
995aa73bf2 | ||
|
|
97012a0a19 | ||
|
|
006f2f9510 | ||
|
|
e849cbfcd8 | ||
|
|
dff1a6daaf | ||
|
|
250dc26514 | ||
|
|
1b327d6abf | ||
|
|
fa11146246 | ||
|
|
da7bfcf3be | ||
|
|
4214ec4239 | ||
|
|
504de47999 | ||
|
|
e185d734d0 | ||
|
|
9b1ef52649 | ||
|
|
5c9fc850d8 | ||
|
|
ff96e52a33 | ||
|
|
78004bcd39 | ||
|
|
b16decf976 | ||
|
|
828636553d | ||
|
|
bfcc27e70f | ||
|
|
7cb7d34d42 | ||
|
|
5276154401 | ||
|
|
dad091441e | ||
|
|
81ff3dadd5 | ||
|
|
f559fb1cae | ||
|
|
8649bbc191 | ||
|
|
d775339948 | ||
|
|
69a5b906e0 | ||
|
|
2d66cc6215 | ||
|
|
8d74baf48c | ||
|
|
d7641c4483 | ||
|
|
ffe54c3af7 | ||
|
|
53df3b5578 | ||
|
|
54c22f1410 | ||
|
|
366b7e04f3 | ||
|
|
a33a4d2bd3 | ||
|
|
eb00d570a8 | ||
|
|
500142f4c8 | ||
|
|
bffd5891cc | ||
|
|
cf2e6f9ff0 | ||
|
|
ed4b8d11e3 | ||
|
|
905c1eac63 | ||
|
|
61872133b1 | ||
|
|
e18f53a5bb | ||
|
|
947fb51963 | ||
|
|
7aeef3f8ba | ||
|
|
b42f6a6364 | ||
|
|
92f4d31dfc | ||
|
|
4f11593f26 | ||
|
|
795d63879f | ||
|
|
1c46eeaf43 | ||
|
|
6f47d12498 | ||
|
|
e6c56675ca | ||
|
|
ff92c96d8d | ||
|
|
fed2afb964 | ||
|
|
b3feff7843 | ||
|
|
edb2fb3458 | ||
|
|
dc51a0bd9d | ||
|
|
34b30ac8c9 | ||
|
|
8122af9096 | ||
|
|
2b267fe4bb | ||
|
|
90bf483976 | ||
|
|
b109f273a5 | ||
|
|
482977a4ac | ||
|
|
515dcdbf1f | ||
|
|
0e033c1d85 | ||
|
|
ad2441de3b | ||
|
|
7a881b3625 | ||
|
|
faf61fdd60 | ||
|
|
2b3d23f77c | ||
|
|
06e1247706 | ||
|
|
6b5fdef8c7 | ||
|
|
48b1621ee4 | ||
|
|
c2ce973f61 | ||
|
|
cac648cc32 | ||
|
|
b9bffcdf4b | ||
|
|
7a1e5c5de7 | ||
|
|
e24dfc856a | ||
|
|
66a115c764 | ||
|
|
a6e80a30dc | ||
|
|
52a2771382 | ||
|
|
6b2b75bc50 | ||
|
|
893baff6c6 | ||
|
|
de62d956dd | ||
|
|
b59cf1f144 | ||
|
|
8b5cc3fb89 | ||
|
|
fbd8c67649 | ||
|
|
c6f6266592 | ||
|
|
ab72640ffd | ||
|
|
905bfff92b | ||
|
|
4ef33d0ffd | ||
|
|
1597363dd1 | ||
|
|
27896a0563 | ||
|
|
bef385cfd1 | ||
|
|
98e0154baa | ||
|
|
01961a798a | ||
|
|
d5a460bd09 | ||
|
|
e94605644f | ||
|
|
8dc206e2d6 | ||
|
|
e8acbbfabf | ||
|
|
17335eb92b | ||
|
|
d2051e6e37 | ||
|
|
0aa0e7c550 | ||
|
|
57935c2296 | ||
|
|
9d4930b29d | ||
|
|
66206e63b3 | ||
|
|
19baccb91a | ||
|
|
98bbd68448 | ||
|
|
45dee58203 | ||
|
|
e2557c44c4 | ||
|
|
f0ed6d73e4 | ||
|
|
47790d1d58 | ||
|
|
9aff5eda38 | ||
|
|
eb2f055e07 | ||
|
|
0998c06195 | ||
|
|
cf22866b2a | ||
|
|
16bb452640 | ||
|
|
6f1d826ef5 | ||
|
|
5a68fb3c3d | ||
|
|
350eb5b6ed | ||
|
|
d48f3b3323 | ||
|
|
7a1491c7b3 | ||
|
|
68deae6bf8 | ||
|
|
2086966a89 | ||
|
|
3aba428b76 | ||
|
|
a11ce38747 | ||
|
|
f7b363ec73 | ||
|
|
4c33a1e9b2 | ||
|
|
c4d9382ac7 | ||
|
|
7df2d990e7 | ||
|
|
900b75ddcd | ||
|
|
a5d199fb1c | ||
|
|
2d8190873f | ||
|
|
3eff793ac2 | ||
|
|
27ce6e1bd2 | ||
|
|
b11c7e632b | ||
|
|
d911be7d80 | ||
|
|
bd056d74cc | ||
|
|
69b65a37ca | ||
|
|
dd52ad8a8a | ||
|
|
59b5ef2df4 | ||
|
|
4a34566f08 | ||
|
|
d81d5ffe06 | ||
|
|
59c9148875 | ||
|
|
40bce3e736 | ||
|
|
d4c0775b4a | ||
|
|
7f983152b7 | ||
|
|
bab4325414 | ||
|
|
0d85c7dd7d | ||
|
|
f756d62b19 | ||
|
|
2d1c1449aa | ||
|
|
f348298acd | ||
|
|
ad47104fc7 | ||
|
|
4d4fb64b59 | ||
|
|
9ce3a6e468 | ||
|
|
34e9e362b9 | ||
|
|
f87cbf73e8 | ||
|
|
362ada2ebb | ||
|
|
b4b7221dab | ||
|
|
2d8dc56f4e | ||
|
|
1cf4313d12 | ||
|
|
7bb8f19b90 | ||
|
|
1263a4e751 | ||
|
|
1a432a9b79 | ||
|
|
8951fdbd59 | ||
|
|
77129367fe | ||
|
|
ae2e1e0933 | ||
|
|
4143f14fbd | ||
|
|
8e3e262c2f | ||
|
|
6f11deab96 | ||
|
|
a17acd7351 | ||
|
|
1cbb4195e4 | ||
|
|
c471dd8297 | ||
|
|
25deffb7d6 | ||
|
|
7eb0e0040e | ||
|
|
15994988be | ||
|
|
ae293a6102 | ||
|
|
5ed4702b62 | ||
|
|
287ddc3424 | ||
|
|
57a4227007 | ||
|
|
c9eacd1bf2 | ||
|
|
5f38162fbb | ||
|
|
c755f75a11 | ||
|
|
ac9eb64501 | ||
|
|
3dd013c13c | ||
|
|
ab0205587a | ||
|
|
70955878c9 | ||
|
|
86f017d92f | ||
|
|
446cc3f9a7 | ||
|
|
6529921d71 | ||
|
|
52e441c111 | ||
|
|
a6b169d336 | ||
|
|
5514cfee6c | ||
|
|
b1de184bda | ||
|
|
af2405ba48 | ||
|
|
bee80330b0 | ||
|
|
f1de8659b7 | ||
|
|
bfc5835d82 | ||
|
|
82bc3e27d6 | ||
|
|
5436a95430 | ||
|
|
2c675f2cbe | ||
|
|
8e9427b0c0 | ||
|
|
edcf78f77c | ||
|
|
8f88d14c07 | ||
|
|
1372801b6f | ||
|
|
2af904f442 | ||
|
|
885b27e0d1 | ||
|
|
0207e4731f | ||
|
|
038b6749a3 | ||
|
|
ead577cbfb | ||
|
|
26e9231e48 | ||
|
|
938ddd1141 | ||
|
|
68080539f7 | ||
|
|
5583b303be | ||
|
|
9301c9b49b | ||
|
|
8d2e210522 | ||
|
|
32661552ff | ||
|
|
9a111a59bf | ||
|
|
f5e472ea9f | ||
|
|
7599e2c793 | ||
|
|
3726f5b9c3 | ||
|
|
dee517217e | ||
|
|
983912e44e | ||
|
|
982390f4b2 | ||
|
|
5e3f51a8b0 | ||
|
|
675ae276f9 | ||
|
|
7899c45176 | ||
|
|
1dbb6feb57 | ||
|
|
661685d136 | ||
|
|
28947a0352 | ||
|
|
bd594684ce | ||
|
|
fda3609873 | ||
|
|
d20db0c546 | ||
|
|
07fb22ae70 | ||
|
|
8d5c3944ad | ||
|
|
7cf28aa9f4 | ||
|
|
731867b73c | ||
|
|
1c6df2d9a2 | ||
|
|
ee5a248a39 | ||
|
|
c174f85656 | ||
|
|
2669aac7c9 | ||
|
|
f1aec74b11 | ||
|
|
1d382b2bf6 | ||
|
|
55cac537b1 | ||
|
|
75258fa195 | ||
|
|
cef2ca6d8e | ||
|
|
4b1651bb3e | ||
|
|
78c116bca9 | ||
|
|
a7c46f5582 | ||
|
|
f947d35bc3 | ||
|
|
a49e0166d4 | ||
|
|
7272b592d5 | ||
|
|
cced739d0e | ||
|
|
54ac46b3e4 | ||
|
|
8f9aa2fd64 | ||
|
|
c7b9cd5853 | ||
|
|
f9dfac55b0 | ||
|
|
b4ad1b5465 | ||
|
|
e2b2b7e255 | ||
|
|
80b691564c | ||
|
|
dfc326b771 | ||
|
|
7cfde70a9f | ||
|
|
20837ba983 | ||
|
|
8dda32a599 | ||
|
|
42ce2a4351 | ||
|
|
6574d5cd29 | ||
|
|
4e9ae9a8f5 | ||
|
|
a9ce3cf733 | ||
|
|
65708a0f12 | ||
|
|
e2a3b5f9ee | ||
|
|
7a752537c5 | ||
|
|
abd0b63fe9 | ||
|
|
7ac7cd452b | ||
|
|
94cecdb8c0 | ||
|
|
a8b35ba971 | ||
|
|
bc1ab84b75 | ||
|
|
acc895d2df | ||
|
|
23dbd0a92f | ||
|
|
c069b5cd97 | ||
|
|
f1dfe50d8b | ||
|
|
7d3820175f | ||
|
|
75032fdbb7 | ||
|
|
23b3ad63cf | ||
|
|
7afd0c86cd | ||
|
|
5f0a341d22 | ||
|
|
2a117376b7 | ||
|
|
2fc750532d | ||
|
|
477be63cff | ||
|
|
39b90357db | ||
|
|
2bd916eb73 | ||
|
|
4d1b450b33 | ||
|
|
cb70812cb7 | ||
|
|
42e030e368 | ||
|
|
f84cdc921d | ||
|
|
02c07e7f4f | ||
|
|
d6ea190688 | ||
|
|
b20487b928 | ||
|
|
004cf5693f | ||
|
|
ef8de6feb0 | ||
|
|
d791ebe634 | ||
|
|
5af4a1817e | ||
|
|
e97b8c55f3 | ||
|
|
99fcb76210 | ||
|
|
d4fd34165e | ||
|
|
bd4741a0a0 | ||
|
|
5945c32646 | ||
|
|
9a9dc2594d | ||
|
|
02547e9475 | ||
|
|
d81a823da1 | ||
|
|
df74bcc885 | ||
|
|
094bcebfa3 | ||
|
|
bb2a16720b | ||
|
|
86d9a0c0f3 | ||
|
|
5cf9d72ed3 | ||
|
|
801605676c | ||
|
|
a9dec75e59 | ||
|
|
08d9d90fe1 | ||
|
|
9749db9235 | ||
|
|
11073aaaa5 | ||
|
|
e35ddc4d53 | ||
|
|
99b06e813e | ||
|
|
7164349944 | ||
|
|
bf43b3adee | ||
|
|
6aa4b3c8a9 | ||
|
|
7a9f7ef95e | ||
|
|
ee1a9d3ec7 | ||
|
|
6a69e58be5 | ||
|
|
6e1d788677 | ||
|
|
24f8f789c5 | ||
|
|
2fb779f990 | ||
|
|
0a9d3cd309 | ||
|
|
977b1aba1c | ||
|
|
a02aeeb906 | ||
|
|
7e2e63137e | ||
|
|
4b35219c27 | ||
|
|
0247c4701d | ||
|
|
e2cd0b8e4f | ||
|
|
ee93171c63 | ||
|
|
ddd2302cb2 | ||
|
|
c96d2288b3 | ||
|
|
6f5a088091 | ||
|
|
9a07797e29 | ||
|
|
055a020d33 |
9
.gitignore
vendored
@ -1,7 +1,10 @@
|
||||
proxy
|
||||
/proxy
|
||||
/goproxy
|
||||
*.exe
|
||||
*.exe~
|
||||
.*
|
||||
*.prof
|
||||
!.gitignore
|
||||
release-*
|
||||
proxy.crt
|
||||
proxy.key
|
||||
/proxy.crt
|
||||
/proxy.key
|
||||
|
||||
13
AUTHORIZATION.md
Normal file
@ -0,0 +1,13 @@
|
||||
# GoProxy special authorization
|
||||
|
||||
1. gproxy uses GPLv3 source code open agreement, without permission, based on the project development software, derivative software, related software, must strictly abide by GPLv3, otherwise once found,
|
||||
Will be harshly punished.
|
||||
|
||||
2. If the company or individual uses the project code to develop related software, derivative software, and does not want to comply with the GPLv3 agreement, need to obtain the author's "GoProxy special authorization" written authorization.
|
||||
|
||||
3. If "GoPro special authorization"is not available on this page,the" GoPro special authorization"is invalid.
|
||||
|
||||
4. A valid authorization number and expiration date are listed below.
|
||||
|
||||
Authorization number | Authorization validity period
|
||||
:--- | :---
|
||||
13
AUTHORIZATION_ZH.md
Normal file
@ -0,0 +1,13 @@
|
||||
# GoProxy特殊授权
|
||||
|
||||
1.goproxy采用GPLv3源代码开放协议,未经许可,基于本项目开发的软件,衍生软件,相关软件,必须严格遵守GPLv3,否则一经发现,
|
||||
将严厉追责.
|
||||
|
||||
2.如果公司或个人使用本项目代码开发相关软件,衍生软件,又不想遵守GPLv3协议,需要取得作者的"GoProxy特殊授权"书面授权.
|
||||
|
||||
3.如果本页面查询不到"GoProxy特殊授权"书面授权信息,则"GoProxy特殊授权"书面授权无效.
|
||||
|
||||
4.下面列出了有效的授权编号和有效期.
|
||||
|
||||
授权编号 | 授权有效期
|
||||
:--- | :---
|
||||
211
CHANGELOG
@ -1,4 +1,215 @@
|
||||
proxy更新日志
|
||||
|
||||
v6.9
|
||||
1.修复了sps的start潜在的crash问题.
|
||||
2.sps代理增加了--parent-tls-single参数用来支持单向tls上级。
|
||||
3.sps代理增加了对单个上级认证信息的支持,如果没有单独设置,就使用全局-A设置.
|
||||
现在上级格式: -P YTpi#2.2.2.2:33080@1
|
||||
说明:
|
||||
YTpi 是经过base64编码的认证信息,比如是http(s)/socks原始认证信息a:b,用户是a密码是b,base64编码之后是:YTpi
|
||||
如果是ss,那么a就是加密方法,b是密码,比如:aes-192-cfb:your_pass,base64编码之后是:YWVzLTE5Mi1jZmI6eW91cl9wYXNz
|
||||
# 是间隔符号,如果有认证信息,必须有#,没有认证信息可以省略#.
|
||||
2.2.2.2:33080 是上级地址
|
||||
@1 是设置权重,可以参考手册权重部分.
|
||||
4.修复了socks5代理错误处理超时的问题.
|
||||
5.修复了http(s)代理错误处理-Z的问题.
|
||||
|
||||
v6.8
|
||||
1.HTTP(S)\SOCKS5代理,API认证功能,发送给认证接口的参数增加了本地IP,local_ip字段,
|
||||
代表用户访问的是本地服务器的哪个IP.
|
||||
2.fix #194 , fix #134 , 代理更稳定.
|
||||
3.增加了一波英文文档.
|
||||
|
||||
v6.6
|
||||
1.优化了limitconn的关闭逻辑,释放更多资源.
|
||||
2.http(s)\socks代理增加了--intelligent,智能模式设置,可以是intelligent|direct|parent三者之一,
|
||||
默认是:intelligent.每个值的含义如下.
|
||||
--intelligent=direct,不在blocked里面的目标都直连.
|
||||
--intelligent=parent,不在direct里面的目标都走上级.
|
||||
--intelligent=intelligent,blocked和direct里面都没有的目标,智能判断是否使用上级访问目标.
|
||||
|
||||
v6.5
|
||||
1.修复了合并企业版遗留的一些bug.
|
||||
|
||||
v6.4
|
||||
1.http(s)代理增加了--jumper参数,可以穿透外部代理连接上级.
|
||||
2.优化了socks5代理UDP功能可能存在的内存占用过多问题.
|
||||
3.优化了jumper,避免某些情况下不能正确返回错误的问题.
|
||||
4.sps代理增加了--jumper参数,可以穿透外部代理连接上级.
|
||||
5.修复了--debug不能正常工作的问题.
|
||||
|
||||
v6.3
|
||||
1.fixed #156
|
||||
2.修复DNS代理,没有定时保存缓存结果到文件.重启会降低查询速度.
|
||||
|
||||
|
||||
v6.2
|
||||
1.修复encrypt.Conn释放内存,导致的潜在panic问题.
|
||||
2.修复了basic认证,处理认证文件没有正确处理注释的bug.
|
||||
3.修正了ssh中转手册参数-A调整为-D.
|
||||
|
||||
v6.1
|
||||
1.黑白名单支持设置顶级域了,比如:com,匹配所有的.com域名
|
||||
2.优化TCPS内存释放.
|
||||
3.优化了域名检查.
|
||||
4.内网穿透增加了TCPS和TOU协议,
|
||||
TCPS提供了多种自定义加密TCP方式传输.
|
||||
TOU提供了TCP over UDP,多种自定义加密UDP方式传输TCP数据.
|
||||
5.优化了DST,防止意外crash.
|
||||
6.修复了mapx的Keys()方法的bug导致内网穿透bridge不稳定的问题.
|
||||
7.修复了部分服务不能绑定IPv6地址的bug.
|
||||
|
||||
v6.0 企业版开源啦
|
||||
本次更新主要是把企业版开源,把企业版代码合并到现在的开源goproxy当中,继续遵循GPLv3,免费开源,
|
||||
之所以直接跳过5.x,用6.0版本号是为了与现有开源版本做一个明显的区分,下面功能主要来自企业版.
|
||||
企业版代码结构更合理,核心与开源版本有很大区别,与此同时企业版有一个core开发库,基于此库可以
|
||||
几行代码实现自己高度定制化的各种网络安全传输服务器和客户端和代理服务器与客户端.与此同时企
|
||||
业版独创了TCPS协议,处于应用层和TCP层之间,可以为应用提供透明化的安全传输功能,另外还对dst协
|
||||
议进行了一些改造,集成到goproxy中,实现了tcp over udp功能,那么除了kcp之外现在还可以选择dst
|
||||
作为底层的tcp over udp的传输.下一步加入插件机制,定制功能可以使用插件方式开发了,热插拔,
|
||||
不需要修改goproxy二进制,可以插件so或者dylib注入.
|
||||
|
||||
1.预编译的二进制增加了armv8支持.
|
||||
2.预编译的mipsle和mips二进制增加了softfloat支持.
|
||||
3.优化连接HTTP(s)上级代理的CONNECT指令,附带更多的信息.
|
||||
4.重构了内网穿透的UDP功能,性能大幅度提升,可以愉快的与异地基友玩依赖UDP的局域网游戏了.
|
||||
5.重构了UDP端口映射,性能大幅度提升.
|
||||
6.HTTP(S)\SOCKS5\SPS代理支持上级负载均衡,可以同时指定多个上级.
|
||||
7.SPS支持HTTP(S)\SOCKS5\SS协议相互转换.
|
||||
8.HTTP(S)\SOCKS5\SPS代理支持限速.
|
||||
9.HTTP(S)\SOCKS5代理支持指定出口IP.
|
||||
10.SOCKS5代理支持级联认证.
|
||||
11.修复了tclient可能意外退出的bug.
|
||||
12.优化了错误捕获,防止意外crash.
|
||||
13.优化了停止服务,释放内存.
|
||||
|
||||
v5.4
|
||||
1.优化了获取本地IP信息导致CPU过高的问题.
|
||||
2.所有服务都增加了--nolog参数,可以关闭日志输出,节省CPU.
|
||||
3.优化sdk,支持并发启动/关闭操作.
|
||||
4.修复了多连接版本的内网穿透,tserver连接不能正确释放的bug.
|
||||
5.内网穿透增加了client/tclient和server/tserver使用代理连接bridge/tbridge的功能,详细内容参考手册.
|
||||
6.TCP端口映射(TCP代理)增加了使用代理连接上级的功能,详细内容参考手册.
|
||||
|
||||
v5.3
|
||||
1.优化了socks_client握手端口判断,避免了sstap测试UDP失败的问题.
|
||||
|
||||
v5.2
|
||||
1.修复了HTTP(S)\SPS反向代理无法正常工作的问题.
|
||||
2.优化了智能判断,减少不必要的DNS解析.
|
||||
3.重构了SOCKS和SPS的UDP功能,基于UDP的游戏加速嗖嗖的.
|
||||
|
||||
v5.1
|
||||
1.优化了kcp默认mtu配置,调整为450.
|
||||
2.优化了HTTP(S)\SOCKS5代理智能判断,更加精确。
|
||||
3.fix #97 , 修复了RemoveProxyHeaders方法忽略了第一行的bug。
|
||||
4.修复了-g参数长格式没有连接符号的bug.
|
||||
5.重构了证书生成功能,不再有任何外部依赖,任何平台都可以独立生成证书.
|
||||
|
||||
v5.0
|
||||
1.修复了SPS多端口无效的bug.
|
||||
2.增加了DNS代理功能,提供安全无污染的DNS解析.
|
||||
|
||||
v4.9
|
||||
1.修复了HTTP Basic代理返回不合适的头部,导致浏览器不会弹框,个别代理插件无法认证的问题.
|
||||
2.内网穿透切换smux到yamux.
|
||||
3.优化了HTTP(S)\SOCKS5代理--always的处理逻辑.
|
||||
|
||||
v4.8
|
||||
1.优化了SPS连接HTTP上级的指令,避免了某些代理不响应的问题.
|
||||
2.SPS功能增加了参数:
|
||||
--disable-http:禁用http(s)代理
|
||||
--disable-socks:禁用socks代理
|
||||
默认都是false(开启).
|
||||
3.重构了部分代码的日志部分,保证了日志按着预期输出.
|
||||
4.修复了sps\http代理初始化服务的时机不正确,导致nil异常的bug.
|
||||
5.优化了sps日志输出.
|
||||
6.--debug参数增加了Profiling功能,可以保存cpu,内存等多种调试数据到文件.
|
||||
7.优化了服务注册,避免了不必要的内存开销.
|
||||
8.增加了Dockerfile和docker安装手册.
|
||||
9.优化了ioCopy避免了内存泄漏,大大提升了内存占用的稳定性.
|
||||
|
||||
|
||||
v4.7
|
||||
1.增加了基于gomobile的sdk,对android/ios/windows/linux/mac提供SDK支持.
|
||||
2.优化了bridge的日志,增加了client和server的掉线日志.
|
||||
3.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求,
|
||||
避免了某些代理服务器返回过多数据导致不能正常通讯的问题.
|
||||
4.去除了鸡肋连接池功能.
|
||||
5.优化了所有服务代码,方便对sdk提供支持.
|
||||
6.增加了SDK手册.
|
||||
7.增加了GUI客户端(windows/web/android/ios)介绍主页.
|
||||
8.SPS\HTTP(s)\Socks代理增加了自定义加密传输,只需要通过参数-z和-Z设置一个密码即可.
|
||||
9.SPS\HTTP(s)\Socks代理增加了压缩传输,只需要通过参数-m和-M设置即可.
|
||||
10.手册增加了SPS\HTTP(s)\Socks自定义加密的使用示例.
|
||||
11.手册增加了SPS\HTTP(s)\Socks压缩传输的使用示例.
|
||||
12.优化了多链接版本的内网穿透,融合了多链接和smux的优点,即能够拥有大的吞吐量,
|
||||
同时又具备mux的心跳机制保证了链接的稳定性.
|
||||
13.手册增加了大量配图.
|
||||
14.优化了socks代理udp上级的设置逻辑,智能判断parent上级填充udp parent.
|
||||
15.优化了项目文件夹结构,使用源码可以直接go get.
|
||||
|
||||
v4.6
|
||||
1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定.
|
||||
2.sps增加了强大的树形级联认证支持,可以轻松构建你的认证代理网络.
|
||||
3.手册增加了6.6对sps认证功能的介绍.
|
||||
|
||||
|
||||
v4.5
|
||||
1.优化了mux内网穿透连接管理逻辑,增强了稳定性.
|
||||
2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp.
|
||||
3.keygen参数增加了用法: proxy keygen usage.
|
||||
4.http(s)/socks5代理,tls增加了自签名证书支持.
|
||||
5.建议升级.
|
||||
v4.4
|
||||
1.增加了协议转换sps功能,代理协议转换使用的是sps子命令(socks+https的缩写),
|
||||
sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理
|
||||
或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口
|
||||
同时支持http(s)和socks5代理,而且http(s)代理支持正向代理和反向代理(SNI),转
|
||||
换后的SOCKS5代理不支持UDP功能;另外对于已经存在的http(s)代理或者socks5代理,
|
||||
支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建
|
||||
加密通道。
|
||||
2.增加了对KCP传输参数的配置,多达17个参数可以自由的配置对kcp传输效率调优。
|
||||
3.内网穿透功能,server和client增加了--session-count参数,可以设置server每个
|
||||
监听端口到bridge打开的session数量,可以设置client到bridge打开的session数量,
|
||||
之前都是1个,现在性能提升N倍,N就是你自己设置的--session-count,这个参数很大
|
||||
程度上解决了多路复用的拥塞问题,v4.4开始默认10个。
|
||||
|
||||
v4.3
|
||||
1.优化了参数keygen生成证书逻辑,避免证书出现特征。
|
||||
2.http(s)和socks代理增加了--dns-address和--dns-ttl参数。
|
||||
用于自己指定proxy访问域名的时候使用的dns(--dns-address)以及解析结果缓存时间(--dns-ttl)秒数,
|
||||
避免系统dns对proxy的干扰,另外缓存功能还能减少dns解析时间提高访问速度。
|
||||
3.优化了http代理的basic认证逻辑。
|
||||
提示:
|
||||
v4.3生成的证书不适用于v4.2及以下版本。
|
||||
|
||||
v4.2
|
||||
1.优化了内网穿透,避免了client意外下线,导致链接信息残留的问题.
|
||||
2.http代理增加了SNI支持,现在http(s)代理模式支持反向代理,支持http(s)透明代理.
|
||||
3.增加了英文手册.
|
||||
|
||||
v4.1
|
||||
1.优化了http(s),socks5代理中的域名智能判断,如果是内网IP,直接走本地网络,提升浏览体验,
|
||||
同时优化了检查机制,判断更快.
|
||||
2.http代理basic认证增加了对https协议的支持,现在basic认证可以控制所有http(s)流量了.
|
||||
3.项目代码增加了依赖类库vendor目录,clone下来就能go build,再也不用担心go get依赖类库
|
||||
失败导致不能编译了.
|
||||
|
||||
v4.0
|
||||
1.内网穿透三端重构了一个multiplexing版本,使用github.com/xtaci/smux实现了tcp链接的多路复用,
|
||||
鼎鼎大名的kcp-go底层就是使用的这个库,基于kcp-go的双边加速工具kcptun的广泛使用已经很好
|
||||
的验证来该库的强大与稳定。multiplexing版的内网穿透对应的子命令分别是server,client,bridge
|
||||
使用方式和参数与之前的子命令tserver,tclient,tserver完全一样,另外server,client增加了
|
||||
压缩传输参数--c,使用压缩传输速度更快。
|
||||
|
||||
v3.9
|
||||
1.增加了守护运行参数--forever,比如: proxy http --forever ,
|
||||
proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后重启子进程.
|
||||
该参数配合后台运行参数--daemon和日志参数--log,可以保障proxy一直在后台执行不会因为意外退出,
|
||||
而且可以通过日志文件看到proxy的输出日志内容.
|
||||
比如: proxy http -p ":9090" --forever --log proxy.log --daemon
|
||||
|
||||
v3.8
|
||||
1.增加了日志输出到文件--log参数,比如: --log proxy.log,日志就会输出到proxy.log方便排除问题.
|
||||
|
||||
|
||||
157
Gopkg.lock
generated
Normal file
@ -0,0 +1,157 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Yawning/chacha20"
|
||||
packages = ["."]
|
||||
revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/alecthomas/template"
|
||||
packages = [
|
||||
".",
|
||||
"parse"
|
||||
]
|
||||
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/alecthomas/units"
|
||||
packages = ["."]
|
||||
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/yamux"
|
||||
packages = ["."]
|
||||
revision = "3520598351bb3500a49ae9563f5539666ae0a27c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/juju/ratelimit"
|
||||
packages = ["."]
|
||||
revision = "59fac5042749a5afb9af70e813da1dd5474f0167"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/klauspost/cpuid"
|
||||
packages = ["."]
|
||||
revision = "ae7887de9fa5d2db4eaa8174a7eff2c1ac00f2da"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/klauspost/reedsolomon"
|
||||
packages = ["."]
|
||||
revision = "6bb6130ff6a76a904c1841707d65603aec9cc288"
|
||||
version = "v1.6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/miekg/dns"
|
||||
packages = ["."]
|
||||
revision = "5a2b9fab83ff0f8bfc99684bd5f43a37abe560f1"
|
||||
version = "v1.0.8"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmylund/go-cache"
|
||||
packages = ["."]
|
||||
revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0"
|
||||
version = "v2.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/templexxx/cpufeat"
|
||||
packages = ["."]
|
||||
revision = "3794dfbfb04749f896b521032f69383f24c3687e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/templexxx/xor"
|
||||
packages = ["."]
|
||||
revision = "0af8e873c554da75f37f2049cdffda804533d44c"
|
||||
version = "0.1.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tjfoc/gmsm"
|
||||
packages = ["sm4"]
|
||||
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/xtaci/kcp-go"
|
||||
packages = ["."]
|
||||
revision = "42bc1dfefff592fdb3affa793980c4f6ab4213e5"
|
||||
version = "v3.25"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
"cast5",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"salsa20",
|
||||
"salsa20/salsa",
|
||||
"ssh",
|
||||
"tea",
|
||||
"twofish",
|
||||
"xtea"
|
||||
]
|
||||
revision = "a8fb68e7206f8c78be19b432c58eb52a6aa34462"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"bpf",
|
||||
"context",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"internal/socks",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"proxy"
|
||||
]
|
||||
revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "dad3d9fb7b6e83d0f9ac8f54670f6334c3a287b4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/time"
|
||||
packages = ["rate"]
|
||||
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/alecthomas/kingpin.v2"
|
||||
packages = ["."]
|
||||
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
|
||||
version = "v2.2.6"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "15e4e23c0695db1458b3dc5514c8765be091a420c923b24bb9e186f43400995f"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
66
Gopkg.toml
Normal file
@ -0,0 +1,66 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/yamux"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/miekg/dns"
|
||||
version = "1.0.8"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pmylund/go-cache"
|
||||
version = "2.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/xtaci/kcp-go"
|
||||
version = "3.25.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/time"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/alecthomas/kingpin.v2"
|
||||
version = "2.2.6"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
1394
README_ZH.md
Normal file
499
config.go
@ -1,48 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
logger "log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"proxy/services"
|
||||
"proxy/utils"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
sdk "github.com/snail007/goproxy/sdk/android-ios"
|
||||
services "github.com/snail007/goproxy/services"
|
||||
httpx "github.com/snail007/goproxy/services/http"
|
||||
keygenx "github.com/snail007/goproxy/services/keygen"
|
||||
mux "github.com/snail007/goproxy/services/mux"
|
||||
socksx "github.com/snail007/goproxy/services/socks"
|
||||
spsx "github.com/snail007/goproxy/services/sps"
|
||||
tcpx "github.com/snail007/goproxy/services/tcp"
|
||||
tunnelx "github.com/snail007/goproxy/services/tunnel"
|
||||
udpx "github.com/snail007/goproxy/services/udp"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
app *kingpin.Application
|
||||
service *services.ServiceItem
|
||||
cmd *exec.Cmd
|
||||
cpuProfilingFile, memProfilingFile, blockProfilingFile,
|
||||
goroutineProfilingFile, threadcreateProfilingFile *os.File
|
||||
isDebug *bool
|
||||
)
|
||||
|
||||
func initConfig() (err error) {
|
||||
//keygen
|
||||
if len(os.Args) > 1 {
|
||||
if os.Args[1] == "keygen" {
|
||||
utils.Keygen()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
//define args
|
||||
tcpArgs := services.TCPArgs{}
|
||||
httpArgs := services.HTTPArgs{}
|
||||
tunnelServerArgs := services.TunnelServerArgs{}
|
||||
tunnelClientArgs := services.TunnelClientArgs{}
|
||||
tunnelBridgeArgs := services.TunnelBridgeArgs{}
|
||||
udpArgs := services.UDPArgs{}
|
||||
socksArgs := services.SocksArgs{}
|
||||
tcpArgs := tcpx.TCPArgs{}
|
||||
httpArgs := httpx.HTTPArgs{}
|
||||
tunnelServerArgs := tunnelx.TunnelServerArgs{}
|
||||
tunnelClientArgs := tunnelx.TunnelClientArgs{}
|
||||
tunnelBridgeArgs := tunnelx.TunnelBridgeArgs{}
|
||||
muxServerArgs := mux.MuxServerArgs{}
|
||||
muxClientArgs := mux.MuxClientArgs{}
|
||||
muxBridgeArgs := mux.MuxBridgeArgs{}
|
||||
udpArgs := udpx.UDPArgs{}
|
||||
socksArgs := socksx.SocksArgs{}
|
||||
spsArgs := spsx.SPSArgs{}
|
||||
dnsArgs := sdk.DNSArgs{}
|
||||
keygenArgs := keygenx.KeygenArgs{}
|
||||
kcpArgs := kcpcfg.KCPConfigArgs{}
|
||||
//build srvice args
|
||||
app = kingpin.New("proxy", "happy with proxy")
|
||||
app.Author("snail").Version(APP_VERSION)
|
||||
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
||||
isDebug = app.Flag("debug", "debug log output").Default("false").Bool()
|
||||
daemon := app.Flag("daemon", "run proxy in background").Default("false").Bool()
|
||||
forever := app.Flag("forever", "run proxy in forever,fail and retry").Default("false").Bool()
|
||||
logfile := app.Flag("log", "log file path").Default("").String()
|
||||
nolog := app.Flag("nolog", "turn off logging").Default("false").Bool()
|
||||
kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String()
|
||||
kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none")
|
||||
kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").Enum("fast3", "fast2", "fast", "normal", "manual")
|
||||
kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("450").Int()
|
||||
kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int()
|
||||
kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int()
|
||||
kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int()
|
||||
kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int()
|
||||
kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int()
|
||||
kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool()
|
||||
kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool()
|
||||
kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int()
|
||||
kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int()
|
||||
kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int()
|
||||
kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int()
|
||||
kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int()
|
||||
kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int()
|
||||
|
||||
//########http#########
|
||||
http := app.Command("http", "proxy on http mode")
|
||||
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').Strings()
|
||||
httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
httpArgs.LocalType = http.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
@ -55,21 +100,33 @@ func initConfig() (err error) {
|
||||
httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||
httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
httpArgs.PoolSize = http.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
||||
httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
httpArgs.Local = http.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||
httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||
httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||
httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||
httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||
httpArgs.KCPKey = http.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
||||
httpArgs.KCPMethod = http.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
||||
httpArgs.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
httpArgs.LocalIPS = http.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int()
|
||||
|
||||
httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
httpArgs.Intelligent = http.Flag("intelligent", "settting intelligent HTTP, SOCKS5 proxy mode, can be <intelligent|direct|parent>").Default("intelligent").Enum("intelligent", "direct", "parent")
|
||||
httpArgs.LoadBalanceMethod = http.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("roundrobin").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
httpArgs.LoadBalanceTimeout = http.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
httpArgs.LoadBalanceRetryTime = http.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
httpArgs.LoadBalanceHashTarget = http.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
httpArgs.LoadBalanceOnlyHA = http.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
httpArgs.RateLimit = http.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
httpArgs.BindListen = http.Flag("bind-listen", "using listener binding IP when connect to target").Short('B').Default("false").Bool()
|
||||
httpArgs.Jumper = http.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
httpArgs.Debug = isDebug
|
||||
//########tcp#########
|
||||
tcp := app.Command("tcp", "proxy on tcp mode")
|
||||
tcpArgs.Parent = tcp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
@ -78,11 +135,9 @@ func initConfig() (err error) {
|
||||
tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int()
|
||||
tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type <tls|tcp|kcp|udp>").Short('T').Enum("tls", "tcp", "udp", "kcp")
|
||||
tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
||||
tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
tcpArgs.KCPKey = tcp.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
||||
tcpArgs.KCPMethod = tcp.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
||||
tcpArgs.Jumper = tcp.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########udp#########
|
||||
udp := app.Command("udp", "proxy on udp mode")
|
||||
@ -91,10 +146,55 @@ func initConfig() (err error) {
|
||||
udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int()
|
||||
udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type <tls|tcp|udp>").Short('T').Enum("tls", "tcp", "udp")
|
||||
udpArgs.PoolSize = udp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
||||
udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
|
||||
//########mux-server#########
|
||||
muxServer := app.Command("server", "proxy on mux server mode")
|
||||
muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('T').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool()
|
||||
muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String()
|
||||
muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||
muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||
muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||
muxServerArgs.Jumper = muxServer.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
muxServerArgs.TCPSMethod = muxServer.Flag("tcps-method", "method of parent tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxServerArgs.TCPSPassword = muxServer.Flag("tcps-password", "password of parent tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxServerArgs.TOUMethod = muxServer.Flag("tou-method", "method of parent tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxServerArgs.TOUPassword = muxServer.Flag("tou-password", "password of parent tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########mux-client#########
|
||||
muxClient := app.Command("client", "proxy on mux client mode")
|
||||
muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('T').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String()
|
||||
muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||
muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||
muxClientArgs.Jumper = muxClient.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
muxClientArgs.TCPSMethod = muxClient.Flag("tcps-method", "method of parent tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxClientArgs.TCPSPassword = muxClient.Flag("tcps-password", "password of parent tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxClientArgs.TOUMethod = muxClient.Flag("tou-method", "method of parent tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxClientArgs.TOUPassword = muxClient.Flag("tou-password", "password of parent tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########mux-bridge#########
|
||||
muxBridge := app.Command("bridge", "proxy on mux bridge mode")
|
||||
muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('t').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxBridgeArgs.TCPSMethod = muxBridge.Flag("tcps-method", "method of local tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxBridgeArgs.TCPSPassword = muxBridge.Flag("tcps-password", "password of local tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxBridgeArgs.TOUMethod = muxBridge.Flag("tou-method", "method of local tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxBridgeArgs.TOUPassword = muxBridge.Flag("tou-password", "password of local tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########tunnel-server#########
|
||||
tunnelServer := app.Command("tserver", "proxy on tunnel server mode")
|
||||
tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
@ -103,8 +203,8 @@ func initConfig() (err error) {
|
||||
tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool()
|
||||
tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String()
|
||||
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as :PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||
tunnelServerArgs.Mux = tunnelServer.Flag("mux", "use multiplexing mode").Default("false").Bool()
|
||||
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||
tunnelServerArgs.Jumper = tunnelServer.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########tunnel-client#########
|
||||
tunnelClient := app.Command("tclient", "proxy on tunnel client mode")
|
||||
@ -113,7 +213,7 @@ func initConfig() (err error) {
|
||||
tunnelClientArgs.KeyFile = tunnelClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tunnelClientArgs.Timeout = tunnelClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelClientArgs.Key = tunnelClient.Flag("k", "key same with server").Default("default").String()
|
||||
tunnelClientArgs.Mux = tunnelClient.Flag("mux", "use multiplexing mode").Default("false").Bool()
|
||||
tunnelClientArgs.Jumper = tunnelClient.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########tunnel-bridge#########
|
||||
tunnelBridge := app.Command("tbridge", "proxy on tunnel bridge mode")
|
||||
@ -121,22 +221,20 @@ func initConfig() (err error) {
|
||||
tunnelBridgeArgs.KeyFile = tunnelBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tunnelBridgeArgs.Timeout = tunnelBridge.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelBridgeArgs.Local = tunnelBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
tunnelBridgeArgs.Mux = tunnelBridge.Flag("mux", "use multiplexing mode").Default("false").Bool()
|
||||
|
||||
//########ssh#########
|
||||
//########socks#########
|
||||
socks := app.Command("socks", "proxy on ssh mode")
|
||||
socksArgs.Parent = socks.Flag("parent", "parent ssh address, such as: \"23.32.32.19:22\"").Default("").Short('P').String()
|
||||
socksArgs.Parent = socks.Flag("parent", "parent ssh address, such as: \"23.32.32.19:22\"").Default("").Short('P').Strings()
|
||||
socksArgs.ParentType = socks.Flag("parent-type", "parent protocol type <tls|tcp|kcp|ssh>").Default("tcp").Short('T').Enum("tls", "tcp", "kcp", "ssh")
|
||||
socksArgs.LocalType = socks.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
socksArgs.Local = socks.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
socksArgs.UDPParent = socks.Flag("udp-parent", "udp parent address, such as: \"23.32.32.19:33090\"").Default("").Short('X').String()
|
||||
socksArgs.UDPLocal = socks.Flag("udp-local", "udp local ip:port to listen").Short('x').Default(":33090").String()
|
||||
socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
socksArgs.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||
socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||
socksArgs.SSHKeyFileSalt = socks.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||
socksArgs.SSHPassword = socks.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||
socksArgs.SSHPassword = socks.Flag("ssh-password", "password for ssh").Short('D').Default("").String()
|
||||
socksArgs.Always = socks.Flag("always", "always use parent proxy").Default("false").Bool()
|
||||
socksArgs.Timeout = socks.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("5000").Int()
|
||||
socksArgs.Interval = socks.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int()
|
||||
@ -144,17 +242,176 @@ func initConfig() (err error) {
|
||||
socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||
socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
socksArgs.KCPKey = socks.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
||||
socksArgs.KCPMethod = socks.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
||||
socksArgs.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
socksArgs.LocalIPS = socks.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||
socksArgs.ParentAuth = socks.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
socksArgs.Intelligent = socks.Flag("intelligent", "settting intelligent HTTP, SOCKS5 proxy mode, can be <intelligent|direct|parent>").Default("intelligent").Enum("intelligent", "direct", "parent")
|
||||
socksArgs.LoadBalanceMethod = socks.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("roundrobin").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
socksArgs.LoadBalanceTimeout = socks.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
socksArgs.LoadBalanceRetryTime = socks.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
socksArgs.LoadBalanceHashTarget = socks.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
socksArgs.LoadBalanceOnlyHA = socks.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
socksArgs.RateLimit = socks.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
socksArgs.BindListen = socks.Flag("bind-listen", "using listener binding IP when connect to target").Short('B').Default("false").Bool()
|
||||
socksArgs.Debug = isDebug
|
||||
|
||||
//########sps#########
|
||||
sps := app.Command("sps", "proxy on socks+http(s) mode")
|
||||
spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').Strings()
|
||||
spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||
spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||
spsArgs.LocalType = sps.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||
spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type <http|socks|ss>").Short('S').Enum("http", "socks", "ss")
|
||||
spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
spsArgs.LocalIPS = sps.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||
spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
spsArgs.SSMethod = sps.Flag("ss-method", "the following methods are supported: aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, rc4-md5-6, chacha20, salsa20, rc4, table, des-cfb, chacha20-ietf; if you use ss client , \"-t tcp\" is required").Short('h').Default("aes-256-cfb").String()
|
||||
spsArgs.SSKey = sps.Flag("ss-key", "if you use ss client , \"-t tcp\" is required").Short('j').Default("sspassword").String()
|
||||
spsArgs.ParentSSMethod = sps.Flag("parent-ss-method", "the following methods are supported: aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, rc4-md5-6, chacha20, salsa20, rc4, table, des-cfb, chacha20-ietf; if you use ss server as parent, \"-T tcp\" is required").Short('H').Default("aes-256-cfb").String()
|
||||
spsArgs.ParentSSKey = sps.Flag("parent-ss-key", "if you use ss server as parent, \"-T tcp\" is required").Short('J').Default("sspassword").String()
|
||||
spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool()
|
||||
spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
|
||||
spsArgs.DisableSS = sps.Flag("disable-ss", "disable ss proxy").Default("false").Bool()
|
||||
spsArgs.LoadBalanceMethod = sps.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("roundrobin").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
spsArgs.LoadBalanceTimeout = sps.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
spsArgs.LoadBalanceRetryTime = sps.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
spsArgs.LoadBalanceHashTarget = sps.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
spsArgs.LoadBalanceOnlyHA = sps.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
spsArgs.RateLimit = sps.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
spsArgs.Jumper = sps.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Default("").String()
|
||||
spsArgs.ParentTLSSingle = sps.Flag("parent-tls-single", "conntect to parent insecure skip verify").Default("false").Bool()
|
||||
spsArgs.Debug = isDebug
|
||||
|
||||
//########dns#########
|
||||
dns := app.Command("dns", "proxy on dns server mode")
|
||||
dnsArgs.Parent = dns.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
dnsArgs.CertFile = dns.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
dnsArgs.KeyFile = dns.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
dnsArgs.CaCertFile = dns.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
dnsArgs.Timeout = dns.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||
dnsArgs.ParentType = dns.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||
dnsArgs.Local = dns.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":53").String()
|
||||
dnsArgs.ParentServiceType = dns.Flag("parent-service-type", "parent service type <http|socks>").Short('S').Enum("http", "socks")
|
||||
dnsArgs.RemoteDNSAddress = dns.Flag("dns-address", "remote dns for resolve doamin").Short('q').Default("8.8.8.8:53").String()
|
||||
dnsArgs.DNSTTL = dns.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
dnsArgs.ParentAuth = dns.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
dnsArgs.ParentKey = dns.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
dnsArgs.ParentCompress = dns.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
dnsArgs.CacheFile = dns.Flag("cache-file", "dns result cached file").Short('f').Default(filepath.Join(path.Dir(os.Args[0]), "cache.dat")).String()
|
||||
dnsArgs.LocalSocks5Port = dns.Flag("socks-port", "local socks5 port").Short('s').Default("65501").String()
|
||||
|
||||
//########keygen#########
|
||||
keygen := app.Command("keygen", "create certificate for proxy")
|
||||
keygenArgs.CommonName = keygen.Flag("cn", "common name").Short('n').Default("").String()
|
||||
keygenArgs.CaName = keygen.Flag("ca", "ca name").Short('C').Default("").String()
|
||||
keygenArgs.CertName = keygen.Flag("cert", "cert name of sign to create").Short('c').Default("").String()
|
||||
keygenArgs.SignDays = keygen.Flag("days", "days of sign").Short('d').Default("365").Int()
|
||||
keygenArgs.Sign = keygen.Flag("sign", "cert is to signin").Short('s').Default("false").Bool()
|
||||
|
||||
//parse args
|
||||
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
flags := log.Ldate
|
||||
|
||||
//set kcp config
|
||||
|
||||
switch *kcpArgs.Mode {
|
||||
case "normal":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1
|
||||
case "fast":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1
|
||||
case "fast2":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1
|
||||
case "fast3":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1
|
||||
}
|
||||
pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New)
|
||||
|
||||
switch *kcpArgs.Crypt {
|
||||
case "sm4":
|
||||
kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||
case "tea":
|
||||
kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||
case "xor":
|
||||
kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||
case "none":
|
||||
kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||
case "aes-128":
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||
case "aes-192":
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||
case "blowfish":
|
||||
kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||
case "twofish":
|
||||
kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||
case "cast5":
|
||||
kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||
case "3des":
|
||||
kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||
case "xtea":
|
||||
kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||
case "salsa20":
|
||||
kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||
default:
|
||||
*kcpArgs.Crypt = "aes"
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass)
|
||||
}
|
||||
//attach kcp config
|
||||
tcpArgs.KCP = kcpArgs
|
||||
httpArgs.KCP = kcpArgs
|
||||
socksArgs.KCP = kcpArgs
|
||||
spsArgs.KCP = kcpArgs
|
||||
muxBridgeArgs.KCP = kcpArgs
|
||||
muxServerArgs.KCP = kcpArgs
|
||||
muxClientArgs.KCP = kcpArgs
|
||||
dnsArgs.KCP = kcpArgs
|
||||
|
||||
log := logger.New(os.Stderr, "", logger.Ldate|logger.Ltime)
|
||||
|
||||
flags := logger.Ldate
|
||||
if *isDebug {
|
||||
flags |= logger.Lshortfile | logger.Lmicroseconds
|
||||
cpuProfilingFile, _ = os.Create("cpu.prof")
|
||||
memProfilingFile, _ = os.Create("memory.prof")
|
||||
blockProfilingFile, _ = os.Create("block.prof")
|
||||
goroutineProfilingFile, _ = os.Create("goroutine.prof")
|
||||
threadcreateProfilingFile, _ = os.Create("threadcreate.prof")
|
||||
pprof.StartCPUProfile(cpuProfilingFile)
|
||||
} else {
|
||||
flags |= logger.Ltime
|
||||
}
|
||||
log.SetFlags(flags)
|
||||
if *nolog {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else if *logfile != "" {
|
||||
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
log.SetOutput(f)
|
||||
}
|
||||
if *daemon {
|
||||
args := []string{}
|
||||
for _, arg := range os.Args[1:] {
|
||||
@ -162,36 +419,122 @@ func initConfig() (err error) {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], args...)
|
||||
cmd = exec.Command(os.Args[0], args...)
|
||||
cmd.Start()
|
||||
fmt.Printf("%s [PID] %d running...\n", os.Args[0], cmd.Process.Pid)
|
||||
f := ""
|
||||
if *forever {
|
||||
f = "forever "
|
||||
}
|
||||
log.Printf("%s%s [PID] %d running...\n", f, os.Args[0], cmd.Process.Pid)
|
||||
os.Exit(0)
|
||||
}
|
||||
if *debug {
|
||||
flags |= log.Lshortfile | log.Lmicroseconds
|
||||
} else {
|
||||
flags |= log.Ltime
|
||||
}
|
||||
log.SetFlags(flags)
|
||||
|
||||
if *logfile != "" {
|
||||
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
if *forever {
|
||||
args := []string{}
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg != "--forever" {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
log.SetOutput(f)
|
||||
} else {
|
||||
poster()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if cmd != nil {
|
||||
cmd.Process.Kill()
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
cmd = exec.Command(os.Args[0], args...)
|
||||
cmdReaderStderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
log.Printf("ERR:%s,restarting...\n", err)
|
||||
continue
|
||||
}
|
||||
cmdReader, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Printf("ERR:%s,restarting...\n", err)
|
||||
continue
|
||||
}
|
||||
scanner := bufio.NewScanner(cmdReader)
|
||||
scannerStdErr := bufio.NewScanner(cmdReaderStderr)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for scanner.Scan() {
|
||||
fmt.Println(scanner.Text())
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for scannerStdErr.Scan() {
|
||||
fmt.Println(scannerStdErr.Text())
|
||||
}
|
||||
}()
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("ERR:%s,restarting...\n", err)
|
||||
continue
|
||||
}
|
||||
pid := cmd.Process.Pid
|
||||
log.Printf("worker %s [PID] %d running...\n", os.Args[0], pid)
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Printf("ERR:%s,restarting...", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("worker %s [PID] %d unexpected exited, restarting...\n", os.Args[0], pid)
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
if *logfile == "" {
|
||||
poster()
|
||||
if *isDebug {
|
||||
log.Println("[profiling] cpu profiling save to file : cpu.prof")
|
||||
log.Println("[profiling] memory profiling save to file : memory.prof")
|
||||
log.Println("[profiling] block profiling save to file : block.prof")
|
||||
log.Println("[profiling] goroutine profiling save to file : goroutine.prof")
|
||||
log.Println("[profiling] threadcreate profiling save to file : threadcreate.prof")
|
||||
}
|
||||
}
|
||||
|
||||
//regist services and run service
|
||||
services.Regist("http", services.NewHTTP(), httpArgs)
|
||||
services.Regist("tcp", services.NewTCP(), tcpArgs)
|
||||
services.Regist("udp", services.NewUDP(), udpArgs)
|
||||
services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs)
|
||||
services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs)
|
||||
services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs)
|
||||
services.Regist("socks", services.NewSocks(), socksArgs)
|
||||
service, err = services.Run(serviceName)
|
||||
switch serviceName {
|
||||
case "http":
|
||||
services.Regist(serviceName, httpx.NewHTTP(), httpArgs, log)
|
||||
case "tcp":
|
||||
services.Regist(serviceName, tcpx.NewTCP(), tcpArgs, log)
|
||||
case "udp":
|
||||
services.Regist(serviceName, udpx.NewUDP(), udpArgs, log)
|
||||
case "tserver":
|
||||
services.Regist(serviceName, tunnelx.NewTunnelServerManager(), tunnelServerArgs, log)
|
||||
case "tclient":
|
||||
services.Regist(serviceName, tunnelx.NewTunnelClient(), tunnelClientArgs, log)
|
||||
case "tbridge":
|
||||
services.Regist(serviceName, tunnelx.NewTunnelBridge(), tunnelBridgeArgs, log)
|
||||
case "server":
|
||||
services.Regist(serviceName, mux.NewMuxServerManager(), muxServerArgs, log)
|
||||
case "client":
|
||||
services.Regist(serviceName, mux.NewMuxClient(), muxClientArgs, log)
|
||||
case "bridge":
|
||||
services.Regist(serviceName, mux.NewMuxBridge(), muxBridgeArgs, log)
|
||||
case "socks":
|
||||
services.Regist(serviceName, socksx.NewSocks(), socksArgs, log)
|
||||
case "sps":
|
||||
services.Regist(serviceName, spsx.NewSPS(), spsArgs, log)
|
||||
case "dns":
|
||||
services.Regist(serviceName, sdk.NewDNS(), dnsArgs, log)
|
||||
case "keygen":
|
||||
services.Regist(serviceName, keygenx.NewKeygen(), keygenArgs, log)
|
||||
}
|
||||
service, err = services.Run(serviceName, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("run service [%s] fail, ERR:%s", serviceName, err)
|
||||
}
|
||||
@ -199,14 +542,16 @@ func initConfig() (err error) {
|
||||
}
|
||||
|
||||
func poster() {
|
||||
fmt.Printf(`
|
||||
######## ######## ####### ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ####
|
||||
######## ######## ## ## ### ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ####### ## ## ##
|
||||
|
||||
v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
||||
fmt.Printf(`Proxy Enterprise Version v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
||||
}
|
||||
func saveProfiling() {
|
||||
goroutine := pprof.Lookup("goroutine")
|
||||
goroutine.WriteTo(goroutineProfilingFile, 1)
|
||||
heap := pprof.Lookup("heap")
|
||||
heap.WriteTo(memProfilingFile, 1)
|
||||
block := pprof.Lookup("block")
|
||||
block.WriteTo(blockProfilingFile, 1)
|
||||
threadcreate := pprof.Lookup("threadcreate")
|
||||
threadcreate.WriteTo(threadcreateProfilingFile, 1)
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
135
core/cs/client/client.go
Normal file
@ -0,0 +1,135 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/snail007/goproxy/core/dst"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
compressconn "github.com/snail007/goproxy/core/lib/transport"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
func TlsConnectHost(host string, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) {
|
||||
h := strings.Split(host, ":")
|
||||
port, _ := strconv.Atoi(h[1])
|
||||
return TlsConnect(h[0], port, timeout, certBytes, keyBytes, caCertBytes)
|
||||
}
|
||||
|
||||
func TlsConnect(host string, port, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) {
|
||||
conf, err := getRequestTlsConfig(certBytes, keyBytes, caCertBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Duration(timeout)*time.Millisecond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return *tls.Client(_conn, conf), err
|
||||
}
|
||||
func TlsConfig(certBytes, keyBytes, caCertBytes []byte) (conf *tls.Config, err error) {
|
||||
return getRequestTlsConfig(certBytes, keyBytes, caCertBytes)
|
||||
}
|
||||
func getRequestTlsConfig(certBytes, keyBytes, caCertBytes []byte) (conf *tls.Config, err error) {
|
||||
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
serverCertPool := x509.NewCertPool()
|
||||
caBytes := certBytes
|
||||
if caCertBytes != nil {
|
||||
caBytes = caCertBytes
|
||||
|
||||
}
|
||||
ok := serverCertPool.AppendCertsFromPEM(caBytes)
|
||||
if !ok {
|
||||
err = errors.New("failed to parse root certificate")
|
||||
}
|
||||
block, _ := pem.Decode(caBytes)
|
||||
if block == nil {
|
||||
panic("failed to parse certificate PEM")
|
||||
}
|
||||
x509Cert, _ := x509.ParseCertificate(block.Bytes)
|
||||
if x509Cert == nil {
|
||||
panic("failed to parse block")
|
||||
}
|
||||
conf = &tls.Config{
|
||||
RootCAs: serverCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: x509Cert.Subject.CommonName,
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: serverCertPool,
|
||||
}
|
||||
for _, rawCert := range rawCerts {
|
||||
cert, _ := x509.ParseCertificate(rawCert)
|
||||
_, err := cert.Verify(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TCPConnectHost(hostAndPort string, timeout int) (conn net.Conn, err error) {
|
||||
conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
func TCPSConnectHost(hostAndPort string, method, password string, compress bool, timeout int) (conn net.Conn, err error) {
|
||||
conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if compress {
|
||||
conn = compressconn.NewCompConn(conn)
|
||||
}
|
||||
conn, err = encryptconn.NewConn(conn, method, password)
|
||||
return
|
||||
}
|
||||
|
||||
func TOUConnectHost(hostAndPort string, method, password string, compress bool, timeout int) (conn net.Conn, err error) {
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Create a DST mux around the packet connection with the default max
|
||||
// packet size.
|
||||
mux := dst.NewMux(udpConn, 0)
|
||||
conn, err = mux.Dial("dst", hostAndPort)
|
||||
if compress {
|
||||
conn = compressconn.NewCompConn(conn)
|
||||
}
|
||||
conn, err = encryptconn.NewConn(conn, method, password)
|
||||
return
|
||||
}
|
||||
func KCPConnectHost(hostAndPort string, config kcpcfg.KCPConfigArgs) (conn net.Conn, err error) {
|
||||
kcpconn, err := kcp.DialWithOptions(hostAndPort, config.Block, *config.DataShard, *config.ParityShard)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kcpconn.SetStreamMode(true)
|
||||
kcpconn.SetWriteDelay(true)
|
||||
kcpconn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion)
|
||||
kcpconn.SetMtu(*config.MTU)
|
||||
kcpconn.SetWindowSize(*config.SndWnd, *config.RcvWnd)
|
||||
kcpconn.SetACKNoDelay(*config.AckNodelay)
|
||||
if *config.NoComp {
|
||||
return kcpconn, err
|
||||
}
|
||||
return compressconn.NewCompStream(kcpconn), err
|
||||
}
|
||||
341
core/cs/server/server.go
Normal file
@ -0,0 +1,341 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
logger "log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
tou "github.com/snail007/goproxy/core/dst"
|
||||
compressconn "github.com/snail007/goproxy/core/lib/transport"
|
||||
transportc "github.com/snail007/goproxy/core/lib/transport"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
||||
type ServerChannel struct {
|
||||
ip string
|
||||
port int
|
||||
Listener *net.Listener
|
||||
UDPListener *net.UDPConn
|
||||
errAcceptHandler func(err error)
|
||||
log *logger.Logger
|
||||
TOUServer *tou.Mux
|
||||
}
|
||||
|
||||
func NewServerChannel(ip string, port int, log *logger.Logger) ServerChannel {
|
||||
return ServerChannel{
|
||||
ip: ip,
|
||||
port: port,
|
||||
log: log,
|
||||
errAcceptHandler: func(err error) {
|
||||
log.Printf("accept error , ERR:%s", err)
|
||||
},
|
||||
}
|
||||
}
|
||||
func NewServerChannelHost(host string, log *logger.Logger) ServerChannel {
|
||||
h, port, _ := net.SplitHostPort(host)
|
||||
p, _ := strconv.Atoi(port)
|
||||
return ServerChannel{
|
||||
ip: h,
|
||||
port: p,
|
||||
log: log,
|
||||
errAcceptHandler: func(err error) {
|
||||
log.Printf("accept error , ERR:%s", err)
|
||||
},
|
||||
}
|
||||
}
|
||||
func (s *ServerChannel) SetErrAcceptHandler(fn func(err error)) {
|
||||
s.errAcceptHandler = fn
|
||||
}
|
||||
func (s *ServerChannel) ListenSingleTLS(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn)) (err error) {
|
||||
return s._ListenTLS(certBytes, keyBytes, caCertBytes, fn, true)
|
||||
|
||||
}
|
||||
func (s *ServerChannel) ListenTLS(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn)) (err error) {
|
||||
return s._ListenTLS(certBytes, keyBytes, caCertBytes, fn, false)
|
||||
}
|
||||
func (s *ServerChannel) _ListenTLS(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn), single bool) (err error) {
|
||||
s.Listener, err = s.listenTLS(s.ip, s.port, certBytes, keyBytes, caCertBytes, single)
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("ListenTLS crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var conn net.Conn
|
||||
conn, err = (*s.Listener).Accept()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("tls connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(conn)
|
||||
}()
|
||||
} else {
|
||||
s.errAcceptHandler(err)
|
||||
(*s.Listener).Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *ServerChannel) listenTLS(ip string, port int, certBytes, keyBytes, caCertBytes []byte, single bool) (ln *net.Listener, err error) {
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
if !single {
|
||||
clientCertPool := x509.NewCertPool()
|
||||
caBytes := certBytes
|
||||
if caCertBytes != nil {
|
||||
caBytes = caCertBytes
|
||||
}
|
||||
ok := clientCertPool.AppendCertsFromPEM(caBytes)
|
||||
if !ok {
|
||||
err = errors.New("failed to parse root certificate")
|
||||
}
|
||||
config.ClientCAs = clientCertPool
|
||||
config.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
_ln, err := tls.Listen("tcp", net.JoinHostPort(ip, fmt.Sprintf("%d", port)), config)
|
||||
if err == nil {
|
||||
ln = &_ln
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *ServerChannel) ListenTCPS(method, password string, compress bool, fn func(conn net.Conn)) (err error) {
|
||||
_, err = encryptconn.NewCipher(method, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.ListenTCP(func(c net.Conn) {
|
||||
if compress {
|
||||
c = transportc.NewCompConn(c)
|
||||
}
|
||||
c, _ = encryptconn.NewConn(c, method, password)
|
||||
fn(c)
|
||||
})
|
||||
}
|
||||
func (s *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
||||
var l net.Listener
|
||||
l, err = net.Listen("tcp", net.JoinHostPort(s.ip, fmt.Sprintf("%d", s.port)))
|
||||
if err == nil {
|
||||
s.Listener = &l
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("ListenTCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var conn net.Conn
|
||||
conn, err = (*s.Listener).Accept()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(conn)
|
||||
}()
|
||||
} else {
|
||||
s.errAcceptHandler(err)
|
||||
(*s.Listener).Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *ServerChannel) ListenUDP(fn func(listener *net.UDPConn, packet []byte, localAddr, srcAddr *net.UDPAddr)) (err error) {
|
||||
addr := &net.UDPAddr{IP: net.ParseIP(s.ip), Port: s.port}
|
||||
l, err := net.ListenUDP("udp", addr)
|
||||
if err == nil {
|
||||
s.UDPListener = l
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("ListenUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var buf = make([]byte, 2048)
|
||||
n, srcAddr, err := (*s.UDPListener).ReadFromUDP(buf)
|
||||
if err == nil {
|
||||
packet := buf[0:n]
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("udp data handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(s.UDPListener, packet, addr, srcAddr)
|
||||
}()
|
||||
} else {
|
||||
s.errAcceptHandler(err)
|
||||
(*s.UDPListener).Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *ServerChannel) ListenKCP(config kcpcfg.KCPConfigArgs, fn func(conn net.Conn), log *logger.Logger) (err error) {
|
||||
lis, err := kcp.ListenWithOptions(net.JoinHostPort(s.ip, fmt.Sprintf("%d", s.port)), config.Block, *config.DataShard, *config.ParityShard)
|
||||
if err == nil {
|
||||
if err = lis.SetDSCP(*config.DSCP); err != nil {
|
||||
log.Println("SetDSCP:", err)
|
||||
return
|
||||
}
|
||||
if err = lis.SetReadBuffer(*config.SockBuf); err != nil {
|
||||
log.Println("SetReadBuffer:", err)
|
||||
return
|
||||
}
|
||||
if err = lis.SetWriteBuffer(*config.SockBuf); err != nil {
|
||||
log.Println("SetWriteBuffer:", err)
|
||||
return
|
||||
}
|
||||
s.Listener = new(net.Listener)
|
||||
*s.Listener = lis
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("ListenKCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
conn, err := lis.AcceptKCP()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("kcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetWriteDelay(true)
|
||||
conn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion)
|
||||
conn.SetMtu(*config.MTU)
|
||||
conn.SetWindowSize(*config.SndWnd, *config.RcvWnd)
|
||||
conn.SetACKNoDelay(*config.AckNodelay)
|
||||
if *config.NoComp {
|
||||
fn(conn)
|
||||
} else {
|
||||
cconn := transportc.NewCompStream(conn)
|
||||
fn(cconn)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
s.errAcceptHandler(err)
|
||||
(*s.Listener).Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ServerChannel) ListenTOU(method, password string, compress bool, fn func(conn net.Conn)) (err error) {
|
||||
addr := &net.UDPAddr{IP: net.ParseIP(s.ip), Port: s.port}
|
||||
s.UDPListener, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
s.log.Println(err)
|
||||
return
|
||||
}
|
||||
s.TOUServer = tou.NewMux(s.UDPListener, 0)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("ListenRUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var conn net.Conn
|
||||
conn, err = (*s.TOUServer).Accept()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if compress {
|
||||
conn = compressconn.NewCompConn(conn)
|
||||
}
|
||||
conn, err = encryptconn.NewConn(conn, method, password)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
s.log.Println(err)
|
||||
return
|
||||
}
|
||||
fn(conn)
|
||||
}()
|
||||
} else {
|
||||
s.errAcceptHandler(err)
|
||||
s.TOUServer.Close()
|
||||
s.UDPListener.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
func (s *ServerChannel) Close() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("close crashed :\n%s\n%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if s.Listener != nil && *s.Listener != nil {
|
||||
(*s.Listener).Close()
|
||||
}
|
||||
if s.TOUServer != nil {
|
||||
s.TOUServer.Close()
|
||||
}
|
||||
if s.UDPListener != nil {
|
||||
s.UDPListener.Close()
|
||||
}
|
||||
}
|
||||
func (s *ServerChannel) Addr() string {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("close crashed :\n%s\n%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if s.Listener != nil && *s.Listener != nil {
|
||||
return (*s.Listener).Addr().String()
|
||||
}
|
||||
|
||||
if s.UDPListener != nil {
|
||||
return s.UDPListener.LocalAddr().String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
49
core/cs/tests/transport_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
ctransport "github.com/snail007/goproxy/core/cs/client"
|
||||
stransport "github.com/snail007/goproxy/core/cs/server"
|
||||
)
|
||||
|
||||
func TestTCPS(t *testing.T) {
|
||||
l := log.New(os.Stderr, "", log.LstdFlags)
|
||||
s := stransport.NewServerChannelHost(":", l)
|
||||
err := s.ListenTCPS("aes-256-cfb", "password", true, func(inconn net.Conn) {
|
||||
buf := make([]byte, 2048)
|
||||
_, err := inconn.Read(buf)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_, err = inconn.Write([]byte("okay"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client, err := ctransport.TCPSConnectHost((*s.Listener).Addr().String(), "aes-256-cfb", "password", true, 1000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
_, err = client.Write([]byte("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := make([]byte, 20)
|
||||
n, err := client.Read(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b[:n]) != "okay" {
|
||||
t.Fatalf("client revecive okay excepted,revecived : %s", string(b[:n]))
|
||||
}
|
||||
}
|
||||
603
core/dst/conn.go
Normal file
@ -0,0 +1,603 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defExpTime = 100 * time.Millisecond // N * (4 * RTT + RTTVar + SYN)
|
||||
expCountClose = 8 // close connection after this many Exps
|
||||
minTimeClose = 5 * time.Second // if at least this long has passed
|
||||
maxInputBuffer = 8 << 20 // bytes
|
||||
muxBufferPackets = 128 // buffer size of channel between mux and reader routine
|
||||
rttMeasureWindow = 32 // number of packets to track for RTT averaging
|
||||
rttMeasureSample = 128 // Sample every ... packet for RTT
|
||||
|
||||
// number of bytes to subtract from MTU when chunking data, to try to
|
||||
// avoid fragmentation
|
||||
sliceOverhead = 8 /*pppoe, similar*/ + 20 /*ipv4*/ + 8 /*udp*/ + 16 /*dst*/
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Properly seed the random number generator that we use for sequence
|
||||
// numbers and stuff.
|
||||
buf := make([]byte, 8)
|
||||
if n, err := crand.Read(buf); n != 8 || err != nil {
|
||||
panic("init random failure")
|
||||
}
|
||||
rand.Seed(int64(binary.BigEndian.Uint64(buf)))
|
||||
}
|
||||
|
||||
// TODO: export this interface when it's usable from the outside
|
||||
type congestionController interface {
|
||||
Ack()
|
||||
NegAck()
|
||||
Exp()
|
||||
SendWindow() int
|
||||
PacketRate() int // PPS
|
||||
UpdateRTT(time.Duration)
|
||||
}
|
||||
|
||||
// Conn is an SDT connection carried over a Mux.
|
||||
type Conn struct {
|
||||
// Set at creation, thereafter immutable:
|
||||
|
||||
mux *Mux
|
||||
dst net.Addr
|
||||
connID connectionID
|
||||
remoteConnID connectionID
|
||||
in chan packet
|
||||
cc congestionController
|
||||
packetSize int
|
||||
closed chan struct{}
|
||||
closeOnce sync.Once
|
||||
|
||||
// Touched by more than one goroutine, needs locking.
|
||||
|
||||
nextSeqNoMut sync.Mutex
|
||||
nextSeqNo sequenceNo
|
||||
|
||||
inbufMut sync.Mutex
|
||||
inbufCond *sync.Cond
|
||||
inbuf bytes.Buffer
|
||||
|
||||
expMut sync.Mutex
|
||||
exp *time.Timer
|
||||
|
||||
sendBuffer *sendBuffer // goroutine safe
|
||||
|
||||
packetDelays [rttMeasureWindow]time.Duration
|
||||
packetDelaysSlot int
|
||||
packetDelaysMut sync.Mutex
|
||||
|
||||
// Owned by the reader routine, needs no locking
|
||||
|
||||
recvBuffer packetList
|
||||
nextRecvSeqNo sequenceNo
|
||||
lastAckedSeqNo sequenceNo
|
||||
lastNegAckedSeqNo sequenceNo
|
||||
expCount int
|
||||
expReset time.Time
|
||||
|
||||
// Only accessed atomically
|
||||
|
||||
packetsIn int64
|
||||
packetsOut int64
|
||||
bytesIn int64
|
||||
bytesOut int64
|
||||
resentPackets int64
|
||||
droppedPackets int64
|
||||
outOfOrderPackets int64
|
||||
|
||||
// Special
|
||||
|
||||
debugResetRecvSeqNo chan sequenceNo
|
||||
}
|
||||
|
||||
func newConn(m *Mux, dst net.Addr) *Conn {
|
||||
conn := &Conn{
|
||||
mux: m,
|
||||
dst: dst,
|
||||
nextSeqNo: sequenceNo(rand.Uint32()),
|
||||
packetSize: maxPacketSize,
|
||||
in: make(chan packet, muxBufferPackets),
|
||||
closed: make(chan struct{}),
|
||||
sendBuffer: newSendBuffer(m),
|
||||
exp: time.NewTimer(defExpTime),
|
||||
debugResetRecvSeqNo: make(chan sequenceNo),
|
||||
expReset: time.Now(),
|
||||
}
|
||||
|
||||
conn.lastAckedSeqNo = conn.nextSeqNo - 1
|
||||
conn.inbufCond = sync.NewCond(&conn.inbufMut)
|
||||
|
||||
conn.cc = newWindowCC()
|
||||
conn.sendBuffer.SetWindowAndRate(conn.cc.SendWindow(), conn.cc.PacketRate())
|
||||
conn.recvBuffer.Resize(128)
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *Conn) start() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
c.reader()
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Conn) reader() {
|
||||
if debugConnection {
|
||||
log.Println(c, "reader() starting")
|
||||
defer log.Println(c, "reader() exiting")
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.closed:
|
||||
// Ack any received but not yet acked messages.
|
||||
c.sendAck(0)
|
||||
|
||||
// Send a shutdown message.
|
||||
c.nextSeqNoMut.Lock()
|
||||
c.mux.write(packet{
|
||||
src: c.connID,
|
||||
dst: c.dst,
|
||||
hdr: header{
|
||||
packetType: typeShutdown,
|
||||
connID: c.remoteConnID,
|
||||
sequenceNo: c.nextSeqNo,
|
||||
},
|
||||
})
|
||||
c.nextSeqNo++
|
||||
c.nextSeqNoMut.Unlock()
|
||||
atomic.AddInt64(&c.packetsOut, 1)
|
||||
atomic.AddInt64(&c.bytesOut, dstHeaderLen)
|
||||
return
|
||||
|
||||
case pkt := <-c.in:
|
||||
atomic.AddInt64(&c.packetsIn, 1)
|
||||
atomic.AddInt64(&c.bytesIn, dstHeaderLen+int64(len(pkt.data)))
|
||||
|
||||
c.expCount = 1
|
||||
|
||||
switch pkt.hdr.packetType {
|
||||
case typeData:
|
||||
c.rcvData(pkt)
|
||||
case typeAck:
|
||||
c.rcvAck(pkt)
|
||||
case typeNegAck:
|
||||
c.rcvNegAck(pkt)
|
||||
case typeShutdown:
|
||||
c.rcvShutdown(pkt)
|
||||
default:
|
||||
log.Println("Unhandled packet", pkt)
|
||||
continue
|
||||
}
|
||||
|
||||
case <-c.exp.C:
|
||||
c.eventExp()
|
||||
c.resetExp()
|
||||
|
||||
case n := <-c.debugResetRecvSeqNo:
|
||||
// Back door for testing
|
||||
c.lastAckedSeqNo = n - 1
|
||||
c.nextRecvSeqNo = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) eventExp() {
|
||||
c.expCount++
|
||||
|
||||
if c.sendBuffer.lost.Len() > 0 || c.sendBuffer.send.Len() > 0 {
|
||||
c.cc.Exp()
|
||||
c.sendBuffer.SetWindowAndRate(c.cc.SendWindow(), c.cc.PacketRate())
|
||||
c.sendBuffer.ScheduleResend()
|
||||
|
||||
if debugConnection {
|
||||
log.Println(c, "did resends due to Exp")
|
||||
}
|
||||
|
||||
if c.expCount > expCountClose && time.Since(c.expReset) > minTimeClose {
|
||||
if debugConnection {
|
||||
log.Println(c, "close due to Exp")
|
||||
}
|
||||
|
||||
// We're shutting down due to repeated exp:s. Don't wait for the
|
||||
// send buffer to drain, which it would otherwise do in
|
||||
// c.Close()..
|
||||
c.sendBuffer.CrashStop()
|
||||
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) rcvAck(pkt packet) {
|
||||
ack := pkt.hdr.sequenceNo
|
||||
|
||||
if debugConnection {
|
||||
log.Printf("%v read Ack %v", c, ack)
|
||||
}
|
||||
|
||||
c.cc.Ack()
|
||||
|
||||
if ack%rttMeasureSample == 0 {
|
||||
if ts := timestamp(binary.BigEndian.Uint32(pkt.data)); ts > 0 {
|
||||
if delay := time.Duration(timestampMicros()-ts) * time.Microsecond; delay > 0 {
|
||||
c.packetDelaysMut.Lock()
|
||||
c.packetDelays[c.packetDelaysSlot] = delay
|
||||
c.packetDelaysSlot = (c.packetDelaysSlot + 1) % len(c.packetDelays)
|
||||
c.packetDelaysMut.Unlock()
|
||||
|
||||
if rtt, n := c.averageDelay(); n > 8 {
|
||||
c.cc.UpdateRTT(rtt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.sendBuffer.Acknowledge(ack)
|
||||
c.sendBuffer.SetWindowAndRate(c.cc.SendWindow(), c.cc.PacketRate())
|
||||
|
||||
c.resetExp()
|
||||
}
|
||||
|
||||
func (c *Conn) averageDelay() (time.Duration, int) {
|
||||
var total time.Duration
|
||||
var n int
|
||||
|
||||
c.packetDelaysMut.Lock()
|
||||
for _, d := range c.packetDelays {
|
||||
if d != 0 {
|
||||
total += d
|
||||
n++
|
||||
}
|
||||
}
|
||||
c.packetDelaysMut.Unlock()
|
||||
|
||||
if n == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
return total / time.Duration(n), n
|
||||
}
|
||||
|
||||
func (c *Conn) rcvNegAck(pkt packet) {
|
||||
nak := pkt.hdr.sequenceNo
|
||||
|
||||
if debugConnection {
|
||||
log.Printf("%v read NegAck %v", c, nak)
|
||||
}
|
||||
|
||||
c.sendBuffer.NegativeAck(nak)
|
||||
|
||||
//c.cc.NegAck()
|
||||
c.resetExp()
|
||||
}
|
||||
|
||||
func (c *Conn) rcvShutdown(pkt packet) {
|
||||
// XXX: We accept shutdown packets somewhat from the future since the
|
||||
// sender will number the shutdown after any packets that might still be
|
||||
// in the write buffer. This should be fixed to let the write buffer empty
|
||||
// on close and reduce the window here.
|
||||
if pkt.LessSeq(c.nextRecvSeqNo + 128) {
|
||||
if debugConnection {
|
||||
log.Println(c, "close due to shutdown")
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) rcvData(pkt packet) {
|
||||
if debugConnection {
|
||||
log.Println(c, "recv data", pkt.hdr)
|
||||
}
|
||||
|
||||
if pkt.LessSeq(c.nextRecvSeqNo) {
|
||||
if debugConnection {
|
||||
log.Printf("%v old packet received; seq %v, expected %v", c, pkt.hdr.sequenceNo, c.nextRecvSeqNo)
|
||||
}
|
||||
atomic.AddInt64(&c.droppedPackets, 1)
|
||||
return
|
||||
}
|
||||
|
||||
if debugConnection {
|
||||
log.Println(c, "into recv buffer:", pkt)
|
||||
}
|
||||
c.recvBuffer.InsertSorted(pkt)
|
||||
if c.recvBuffer.LowestSeq() == c.nextRecvSeqNo {
|
||||
for _, pkt := range c.recvBuffer.PopSequence(^sequenceNo(0)) {
|
||||
if debugConnection {
|
||||
log.Println(c, "from recv buffer:", pkt)
|
||||
}
|
||||
|
||||
// An in-sequence packet.
|
||||
|
||||
c.nextRecvSeqNo = pkt.hdr.sequenceNo + 1
|
||||
|
||||
c.sendAck(pkt.hdr.timestamp)
|
||||
|
||||
c.inbufMut.Lock()
|
||||
for c.inbuf.Len() > len(pkt.data)+maxInputBuffer {
|
||||
c.inbufCond.Wait()
|
||||
select {
|
||||
case <-c.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
c.inbuf.Write(pkt.data)
|
||||
c.inbufCond.Broadcast()
|
||||
c.inbufMut.Unlock()
|
||||
}
|
||||
} else {
|
||||
if debugConnection {
|
||||
log.Printf("%v lost; seq %v, expected %v", c, pkt.hdr.sequenceNo, c.nextRecvSeqNo)
|
||||
}
|
||||
c.recvBuffer.InsertSorted(pkt)
|
||||
c.sendNegAck()
|
||||
atomic.AddInt64(&c.outOfOrderPackets, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) sendAck(ts timestamp) {
|
||||
if c.lastAckedSeqNo == c.nextRecvSeqNo {
|
||||
return
|
||||
}
|
||||
|
||||
var buf [4]byte
|
||||
binary.BigEndian.PutUint32(buf[:], uint32(ts))
|
||||
c.mux.write(packet{
|
||||
src: c.connID,
|
||||
dst: c.dst,
|
||||
hdr: header{
|
||||
packetType: typeAck,
|
||||
connID: c.remoteConnID,
|
||||
sequenceNo: c.nextRecvSeqNo,
|
||||
},
|
||||
data: buf[:],
|
||||
})
|
||||
|
||||
atomic.AddInt64(&c.packetsOut, 1)
|
||||
atomic.AddInt64(&c.bytesOut, dstHeaderLen)
|
||||
if debugConnection {
|
||||
log.Printf("%v send Ack %v", c, c.nextRecvSeqNo)
|
||||
}
|
||||
|
||||
c.lastAckedSeqNo = c.nextRecvSeqNo
|
||||
}
|
||||
|
||||
func (c *Conn) sendNegAck() {
|
||||
if c.lastNegAckedSeqNo == c.nextRecvSeqNo {
|
||||
return
|
||||
}
|
||||
|
||||
c.mux.write(packet{
|
||||
src: c.connID,
|
||||
dst: c.dst,
|
||||
hdr: header{
|
||||
packetType: typeNegAck,
|
||||
connID: c.remoteConnID,
|
||||
sequenceNo: c.nextRecvSeqNo,
|
||||
},
|
||||
})
|
||||
|
||||
atomic.AddInt64(&c.packetsOut, 1)
|
||||
atomic.AddInt64(&c.bytesOut, dstHeaderLen)
|
||||
if debugConnection {
|
||||
log.Printf("%v send NegAck %v", c, c.nextRecvSeqNo)
|
||||
}
|
||||
|
||||
c.lastNegAckedSeqNo = c.nextRecvSeqNo
|
||||
}
|
||||
|
||||
func (c *Conn) resetExp() {
|
||||
d, _ := c.averageDelay()
|
||||
d = d*4 + 10*time.Millisecond
|
||||
|
||||
if d < defExpTime {
|
||||
d = defExpTime
|
||||
}
|
||||
|
||||
c.expMut.Lock()
|
||||
c.exp.Reset(d)
|
||||
c.expMut.Unlock()
|
||||
}
|
||||
|
||||
// String returns a string representation of the connection.
|
||||
func (c *Conn) String() string {
|
||||
return fmt.Sprintf("%v/%v/%v", c.connID, c.LocalAddr(), c.RemoteAddr())
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
// Read can be made to time out and return a Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
n = 0
|
||||
err = io.EOF
|
||||
}
|
||||
}()
|
||||
c.inbufMut.Lock()
|
||||
defer c.inbufMut.Unlock()
|
||||
for c.inbuf.Len() == 0 {
|
||||
select {
|
||||
case <-c.closed:
|
||||
return 0, io.EOF
|
||||
default:
|
||||
}
|
||||
c.inbufCond.Wait()
|
||||
}
|
||||
return c.inbuf.Read(b)
|
||||
}
|
||||
|
||||
// Write writes data to the connection.
|
||||
// Write can be made to time out and return a Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-c.closed:
|
||||
return 0, ErrClosedConn
|
||||
default:
|
||||
}
|
||||
|
||||
sent := 0
|
||||
sliceSize := c.packetSize - sliceOverhead
|
||||
for i := 0; i < len(b); i += sliceSize {
|
||||
nxt := i + sliceSize
|
||||
if nxt > len(b) {
|
||||
nxt = len(b)
|
||||
}
|
||||
slice := b[i:nxt]
|
||||
sliceCopy := c.mux.buffers.Get().([]byte)[:len(slice)]
|
||||
copy(sliceCopy, slice)
|
||||
|
||||
c.nextSeqNoMut.Lock()
|
||||
pkt := packet{
|
||||
src: c.connID,
|
||||
dst: c.dst,
|
||||
hdr: header{
|
||||
packetType: typeData,
|
||||
sequenceNo: c.nextSeqNo,
|
||||
connID: c.remoteConnID,
|
||||
},
|
||||
data: sliceCopy,
|
||||
}
|
||||
c.nextSeqNo++
|
||||
c.nextSeqNoMut.Unlock()
|
||||
|
||||
if err := c.sendBuffer.Write(pkt); err != nil {
|
||||
return sent, err
|
||||
}
|
||||
|
||||
atomic.AddInt64(&c.packetsOut, 1)
|
||||
atomic.AddInt64(&c.bytesOut, int64(len(slice)+dstHeaderLen))
|
||||
|
||||
sent += len(slice)
|
||||
c.resetExp()
|
||||
}
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||
func (c *Conn) Close() error {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
c.closeOnce.Do(func() {
|
||||
if debugConnection {
|
||||
log.Println(c, "explicit close start")
|
||||
defer log.Println(c, "explicit close done")
|
||||
}
|
||||
|
||||
// XXX: Ugly hack to implement lingering sockets...
|
||||
time.Sleep(4 * defExpTime)
|
||||
|
||||
c.sendBuffer.Stop()
|
||||
c.mux.removeConn(c)
|
||||
close(c.closed)
|
||||
|
||||
c.inbufMut.Lock()
|
||||
c.inbufCond.Broadcast()
|
||||
c.inbufMut.Unlock()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
return c.mux.Addr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *Conn) RemoteAddr() net.Addr {
|
||||
return c.dst
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection. It is equivalent to calling both
|
||||
// SetReadDeadline and SetWriteDeadline.
|
||||
//
|
||||
// A deadline is an absolute time after which I/O operations
|
||||
// fail with a timeout (see type Error) instead of
|
||||
// blocking. The deadline applies to all future I/O, not just
|
||||
// the immediately following call to Read or Write.
|
||||
//
|
||||
// An idle timeout can be implemented by repeatedly extending
|
||||
// the deadline after successful Read or Write calls.
|
||||
//
|
||||
// A zero value for t means I/O operations will not time out.
|
||||
//
|
||||
// BUG(jb): SetDeadline is not implemented.
|
||||
func (c *Conn) SetDeadline(t time.Time) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls.
|
||||
// A zero value for t means Read will not time out.
|
||||
//
|
||||
// BUG(jb): SetReadDeadline is not implemented.
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls.
|
||||
// Even if write times out, it may return n > 0, indicating that
|
||||
// some of the data was successfully written.
|
||||
// A zero value for t means Write will not time out.
|
||||
//
|
||||
// BUG(jb): SetWriteDeadline is not implemented.
|
||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
DataPacketsIn int64
|
||||
DataPacketsOut int64
|
||||
DataBytesIn int64
|
||||
DataBytesOut int64
|
||||
ResentPackets int64
|
||||
DroppedPackets int64
|
||||
OutOfOrderPackets int64
|
||||
}
|
||||
|
||||
// String returns a printable represetnation of the Statistics.
|
||||
func (s Statistics) String() string {
|
||||
return fmt.Sprintf("PktsIn: %d, PktsOut: %d, BytesIn: %d, BytesOut: %d, PktsResent: %d, PktsDropped: %d, PktsOutOfOrder: %d",
|
||||
s.DataPacketsIn, s.DataPacketsOut, s.DataBytesIn, s.DataBytesOut, s.ResentPackets, s.DroppedPackets, s.OutOfOrderPackets)
|
||||
}
|
||||
|
||||
// GetStatistics returns a snapsht of the current connection statistics.
|
||||
func (c *Conn) GetStatistics() Statistics {
|
||||
return Statistics{
|
||||
DataPacketsIn: atomic.LoadInt64(&c.packetsIn),
|
||||
DataPacketsOut: atomic.LoadInt64(&c.packetsOut),
|
||||
DataBytesIn: atomic.LoadInt64(&c.bytesIn),
|
||||
DataBytesOut: atomic.LoadInt64(&c.bytesOut),
|
||||
ResentPackets: atomic.LoadInt64(&c.resentPackets),
|
||||
DroppedPackets: atomic.LoadInt64(&c.droppedPackets),
|
||||
OutOfOrderPackets: atomic.LoadInt64(&c.outOfOrderPackets),
|
||||
}
|
||||
}
|
||||
29
core/dst/cookie.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
var cookieKey = make([]byte, 16)
|
||||
|
||||
func init() {
|
||||
_, err := rand.Reader.Read(cookieKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func cookie(remote net.Addr) uint32 {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(remote.String()))
|
||||
hash.Write(cookieKey)
|
||||
bs := hash.Sum(nil)
|
||||
return binary.BigEndian.Uint32(bs)
|
||||
}
|
||||
26
core/dst/debug.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
debugConnection bool
|
||||
debugMux bool
|
||||
debugCC bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
debug := make(map[string]bool)
|
||||
for _, s := range strings.Split(os.Getenv("DSTDEBUG"), ",") {
|
||||
debug[strings.TrimSpace(s)] = true
|
||||
}
|
||||
debugConnection = debug["conn"]
|
||||
debugMux = debug["mux"]
|
||||
debugCC = debug["cc"]
|
||||
}
|
||||
12
core/dst/doc.go
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package dst implements the Datagram Stream Transfer protocol.
|
||||
|
||||
DST is a way to get reliable stream connections (like TCP) on top of UDP.
|
||||
|
||||
*/
|
||||
package dst
|
||||
23
core/dst/errors.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
// Error represents the various dst-internal error conditions.
|
||||
type Error struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
// Error returns a string representation of the error.
|
||||
func (e Error) Error() string {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
var (
|
||||
ErrClosedConn = &Error{"operation on closed connection"}
|
||||
ErrClosedMux = &Error{"operation on closed mux"}
|
||||
ErrHandshakeTimeout = &Error{"handshake timeout"}
|
||||
ErrNotDST = &Error{"network is not dst"}
|
||||
ErrNotImplemented = &Error{"not implemented"}
|
||||
)
|
||||
429
core/dst/mux.go
Normal file
@ -0,0 +1,429 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxIncomingRequests = 1024
|
||||
maxPacketSize = 500
|
||||
handshakeTimeout = 5 * time.Second
|
||||
handshakeInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
// Mux is a UDP multiplexer of DST connections.
|
||||
type Mux struct {
|
||||
conn net.PacketConn
|
||||
packetSize int
|
||||
|
||||
conns map[connectionID]*Conn
|
||||
handshakes map[connectionID]chan packet
|
||||
connsMut sync.Mutex
|
||||
|
||||
incoming chan *Conn
|
||||
closed chan struct{}
|
||||
closeOnce sync.Once
|
||||
|
||||
buffers *sync.Pool
|
||||
}
|
||||
|
||||
// NewMux creates a new DST Mux on top of a packet connection.
|
||||
func NewMux(conn net.PacketConn, packetSize int) *Mux {
|
||||
if packetSize <= 0 {
|
||||
packetSize = maxPacketSize
|
||||
}
|
||||
m := &Mux{
|
||||
conn: conn,
|
||||
packetSize: packetSize,
|
||||
conns: map[connectionID]*Conn{},
|
||||
handshakes: make(map[connectionID]chan packet),
|
||||
incoming: make(chan *Conn, maxIncomingRequests),
|
||||
closed: make(chan struct{}),
|
||||
buffers: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, packetSize)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Attempt to maximize buffer space. Start at 16 MB and work downwards 0.5
|
||||
// MB at a time.
|
||||
|
||||
if conn, ok := conn.(*net.UDPConn); ok {
|
||||
for buf := 16384 * 1024; buf >= 512*1024; buf -= 512 * 1024 {
|
||||
err := conn.SetReadBuffer(buf)
|
||||
if err == nil {
|
||||
if debugMux {
|
||||
log.Println(m, "read buffer is", buf)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
for buf := 16384 * 1024; buf >= 512*1024; buf -= 512 * 1024 {
|
||||
err := conn.SetWriteBuffer(buf)
|
||||
if err == nil {
|
||||
if debugMux {
|
||||
log.Println(m, "write buffer is", buf)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
m.readerLoop()
|
||||
}()
|
||||
return m
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (m *Mux) Accept() (net.Conn, error) {
|
||||
return m.AcceptDST()
|
||||
}
|
||||
|
||||
// AcceptDST waits for and returns the next connection to the listener.
|
||||
func (m *Mux) AcceptDST() (*Conn, error) {
|
||||
conn, ok := <-m.incoming
|
||||
if !ok {
|
||||
return nil, ErrClosedMux
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Close closes the listener.
|
||||
// Any blocked Accept operations will be unblocked and return errors.
|
||||
func (m *Mux) Close() error {
|
||||
var err error = ErrClosedMux
|
||||
m.closeOnce.Do(func() {
|
||||
err = m.conn.Close()
|
||||
close(m.incoming)
|
||||
close(m.closed)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (m *Mux) Addr() net.Addr {
|
||||
return m.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
//
|
||||
// Network must be "dst".
|
||||
//
|
||||
// Addresses have the form host:port. If host is a literal IPv6 address or
|
||||
// host name, it must be enclosed in square brackets as in "[::1]:80",
|
||||
// "[ipv6-host]:http" or "[ipv6-host%zone]:80". The functions JoinHostPort and
|
||||
// SplitHostPort manipulate addresses in this form.
|
||||
//
|
||||
// Examples:
|
||||
// Dial("dst", "12.34.56.78:80")
|
||||
// Dial("dst", "google.com:http")
|
||||
// Dial("dst", "[2001:db8::1]:http")
|
||||
// Dial("dst", "[fe80::1%lo0]:80")
|
||||
func (m *Mux) Dial(network, addr string) (net.Conn, error) {
|
||||
return m.DialDST(network, addr)
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
//
|
||||
// Network must be "dst".
|
||||
//
|
||||
// Addresses have the form host:port. If host is a literal IPv6 address or
|
||||
// host name, it must be enclosed in square brackets as in "[::1]:80",
|
||||
// "[ipv6-host]:http" or "[ipv6-host%zone]:80". The functions JoinHostPort and
|
||||
// SplitHostPort manipulate addresses in this form.
|
||||
//
|
||||
// Examples:
|
||||
// Dial("dst", "12.34.56.78:80")
|
||||
// Dial("dst", "google.com:http")
|
||||
// Dial("dst", "[2001:db8::1]:http")
|
||||
// Dial("dst", "[fe80::1%lo0]:80")
|
||||
func (m *Mux) DialDST(network, addr string) (*Conn, error) {
|
||||
if network != "dst" {
|
||||
return nil, ErrNotDST
|
||||
}
|
||||
|
||||
dst, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := make(chan packet)
|
||||
|
||||
m.connsMut.Lock()
|
||||
connID := m.newConnID()
|
||||
m.handshakes[connID] = resp
|
||||
m.connsMut.Unlock()
|
||||
|
||||
conn, err := m.clientHandshake(dst, connID, resp)
|
||||
|
||||
m.connsMut.Lock()
|
||||
defer m.connsMut.Unlock()
|
||||
delete(m.handshakes, connID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.conns[connID] = conn
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// handshake performs the client side handshake (i.e. Dial)
|
||||
func (m *Mux) clientHandshake(dst net.Addr, connID connectionID, resp chan packet) (*Conn, error) {
|
||||
if debugMux {
|
||||
log.Printf("%v dial %v connID %v", m, dst, connID)
|
||||
}
|
||||
|
||||
nextHandshake := time.NewTimer(0)
|
||||
defer nextHandshake.Stop()
|
||||
|
||||
handshakeTimeout := time.NewTimer(handshakeTimeout)
|
||||
defer handshakeTimeout.Stop()
|
||||
|
||||
var remoteCookie uint32
|
||||
seqNo := randomSeqNo()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.closed:
|
||||
// Failure. The mux has been closed.
|
||||
return nil, ErrClosedConn
|
||||
|
||||
case <-handshakeTimeout.C:
|
||||
// Handshake timeout. Close and abort.
|
||||
return nil, ErrHandshakeTimeout
|
||||
|
||||
case <-nextHandshake.C:
|
||||
// Send a handshake request.
|
||||
|
||||
m.write(packet{
|
||||
src: connID,
|
||||
dst: dst,
|
||||
hdr: header{
|
||||
packetType: typeHandshake,
|
||||
flags: flagRequest,
|
||||
connID: 0,
|
||||
sequenceNo: seqNo,
|
||||
timestamp: timestampMicros(),
|
||||
},
|
||||
data: handshakeData{uint32(m.packetSize), connID, remoteCookie}.marshal(),
|
||||
})
|
||||
nextHandshake.Reset(handshakeInterval)
|
||||
|
||||
case pkt := <-resp:
|
||||
hd := unmarshalHandshakeData(pkt.data)
|
||||
|
||||
if pkt.hdr.flags&flagCookie == flagCookie {
|
||||
// We should resend the handshake request with a different cookie value.
|
||||
remoteCookie = hd.cookie
|
||||
nextHandshake.Reset(0)
|
||||
} else if pkt.hdr.flags&flagResponse == flagResponse {
|
||||
// Successfull handshake response.
|
||||
conn := newConn(m, dst)
|
||||
|
||||
conn.connID = connID
|
||||
conn.remoteConnID = hd.connID
|
||||
conn.nextRecvSeqNo = pkt.hdr.sequenceNo + 1
|
||||
conn.packetSize = int(hd.packetSize)
|
||||
if conn.packetSize > m.packetSize {
|
||||
conn.packetSize = m.packetSize
|
||||
}
|
||||
|
||||
conn.nextSeqNo = seqNo + 1
|
||||
|
||||
conn.start()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) readerLoop() {
|
||||
buf := make([]byte, m.packetSize)
|
||||
for {
|
||||
buf = buf[:cap(buf)]
|
||||
n, from, err := m.conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
m.Close()
|
||||
return
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
hdr := unmarshalHeader(buf)
|
||||
|
||||
var bufCopy []byte
|
||||
if len(buf) > dstHeaderLen {
|
||||
bufCopy = m.buffers.Get().([]byte)[:len(buf)-dstHeaderLen]
|
||||
copy(bufCopy, buf[dstHeaderLen:])
|
||||
}
|
||||
|
||||
pkt := packet{hdr: hdr, data: bufCopy}
|
||||
if debugMux {
|
||||
log.Println(m, "read", pkt)
|
||||
}
|
||||
|
||||
if hdr.packetType == typeHandshake {
|
||||
m.incomingHandshake(from, hdr, bufCopy)
|
||||
} else {
|
||||
m.connsMut.Lock()
|
||||
conn, ok := m.conns[hdr.connID]
|
||||
m.connsMut.Unlock()
|
||||
|
||||
if ok {
|
||||
conn.in <- packet{
|
||||
dst: nil,
|
||||
hdr: hdr,
|
||||
data: bufCopy,
|
||||
}
|
||||
} else if debugMux && hdr.packetType != typeShutdown {
|
||||
log.Printf("packet %v for unknown conn %v", hdr, hdr.connID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) incomingHandshake(from net.Addr, hdr header, data []byte) {
|
||||
if hdr.connID == 0 {
|
||||
// A new incoming handshake request.
|
||||
m.incomingHandshakeRequest(from, hdr, data)
|
||||
} else {
|
||||
// A response to an ongoing handshake.
|
||||
m.incomingHandshakeResponse(from, hdr, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) incomingHandshakeRequest(from net.Addr, hdr header, data []byte) {
|
||||
if hdr.flags&flagRequest != flagRequest {
|
||||
log.Printf("Handshake pattern with flags 0x%x to connID zero", hdr.flags)
|
||||
return
|
||||
}
|
||||
|
||||
hd := unmarshalHandshakeData(data)
|
||||
|
||||
correctCookie := cookie(from)
|
||||
if hd.cookie != correctCookie {
|
||||
// Incorrect or missing SYN cookie. Send back a handshake
|
||||
// with the expected one.
|
||||
m.write(packet{
|
||||
dst: from,
|
||||
hdr: header{
|
||||
packetType: typeHandshake,
|
||||
flags: flagResponse | flagCookie,
|
||||
connID: hd.connID,
|
||||
timestamp: timestampMicros(),
|
||||
},
|
||||
data: handshakeData{
|
||||
packetSize: uint32(m.packetSize),
|
||||
cookie: correctCookie,
|
||||
}.marshal(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
seqNo := randomSeqNo()
|
||||
|
||||
m.connsMut.Lock()
|
||||
connID := m.newConnID()
|
||||
|
||||
conn := newConn(m, from)
|
||||
conn.connID = connID
|
||||
conn.remoteConnID = hd.connID
|
||||
conn.nextSeqNo = seqNo + 1
|
||||
conn.nextRecvSeqNo = hdr.sequenceNo + 1
|
||||
conn.packetSize = int(hd.packetSize)
|
||||
if conn.packetSize > m.packetSize {
|
||||
conn.packetSize = m.packetSize
|
||||
}
|
||||
conn.start()
|
||||
|
||||
m.conns[connID] = conn
|
||||
m.connsMut.Unlock()
|
||||
|
||||
m.write(packet{
|
||||
dst: from,
|
||||
hdr: header{
|
||||
packetType: typeHandshake,
|
||||
flags: flagResponse,
|
||||
connID: hd.connID,
|
||||
sequenceNo: seqNo,
|
||||
timestamp: timestampMicros(),
|
||||
},
|
||||
data: handshakeData{
|
||||
connID: conn.connID,
|
||||
packetSize: uint32(conn.packetSize),
|
||||
}.marshal(),
|
||||
})
|
||||
|
||||
m.incoming <- conn
|
||||
}
|
||||
|
||||
func (m *Mux) incomingHandshakeResponse(from net.Addr, hdr header, data []byte) {
|
||||
m.connsMut.Lock()
|
||||
handShake, ok := m.handshakes[hdr.connID]
|
||||
m.connsMut.Unlock()
|
||||
|
||||
if ok {
|
||||
// This is a response to a handshake in progress.
|
||||
handShake <- packet{
|
||||
dst: nil,
|
||||
hdr: hdr,
|
||||
data: data,
|
||||
}
|
||||
} else if debugMux && hdr.packetType != typeShutdown {
|
||||
log.Printf("Handshake packet %v for unknown conn %v", hdr, hdr.connID)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) write(pkt packet) (int, error) {
|
||||
buf := m.buffers.Get().([]byte)
|
||||
buf = buf[:dstHeaderLen+len(pkt.data)]
|
||||
pkt.hdr.marshal(buf)
|
||||
copy(buf[dstHeaderLen:], pkt.data)
|
||||
if debugMux {
|
||||
log.Println(m, "write", pkt)
|
||||
}
|
||||
n, err := m.conn.WriteTo(buf, pkt.dst)
|
||||
m.buffers.Put(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (m *Mux) String() string {
|
||||
return fmt.Sprintf("Mux-%v", m.Addr())
|
||||
}
|
||||
|
||||
// Find a unique connection ID
|
||||
func (m *Mux) newConnID() connectionID {
|
||||
for {
|
||||
connID := randomConnID()
|
||||
if _, ok := m.conns[connID]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := m.handshakes[connID]; ok {
|
||||
continue
|
||||
}
|
||||
return connID
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mux) removeConn(c *Conn) {
|
||||
m.connsMut.Lock()
|
||||
delete(m.conns, c.connID)
|
||||
m.connsMut.Unlock()
|
||||
}
|
||||
119
core/dst/packetlist.go
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
type packetList struct {
|
||||
packets []packet
|
||||
slot int
|
||||
}
|
||||
|
||||
// CutLessSeq cuts packets from the start of the list with sequence numbers
|
||||
// lower than seq. Returns the number of packets that were cut.
|
||||
func (l *packetList) CutLessSeq(seq sequenceNo) int {
|
||||
var i, cut int
|
||||
for i = range l.packets {
|
||||
if i == l.slot {
|
||||
break
|
||||
}
|
||||
if !l.packets[i].LessSeq(seq) {
|
||||
break
|
||||
}
|
||||
cut++
|
||||
}
|
||||
if cut > 0 {
|
||||
l.Cut(cut)
|
||||
}
|
||||
return cut
|
||||
}
|
||||
|
||||
func (l *packetList) Cut(n int) {
|
||||
copy(l.packets, l.packets[n:])
|
||||
l.slot -= n
|
||||
}
|
||||
|
||||
func (l *packetList) Full() bool {
|
||||
return l.slot == len(l.packets)
|
||||
}
|
||||
|
||||
func (l *packetList) All() []packet {
|
||||
return l.packets[:l.slot]
|
||||
}
|
||||
|
||||
func (l *packetList) Append(pkt packet) bool {
|
||||
if l.slot == len(l.packets) {
|
||||
return false
|
||||
}
|
||||
l.packets[l.slot] = pkt
|
||||
l.slot++
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *packetList) AppendAll(pkts []packet) {
|
||||
l.packets = append(l.packets[:l.slot], pkts...)
|
||||
l.slot += len(pkts)
|
||||
}
|
||||
|
||||
func (l *packetList) Cap() int {
|
||||
return len(l.packets)
|
||||
}
|
||||
|
||||
func (l *packetList) Len() int {
|
||||
return l.slot
|
||||
}
|
||||
|
||||
func (l *packetList) Resize(s int) {
|
||||
if s <= cap(l.packets) {
|
||||
l.packets = l.packets[:s]
|
||||
} else {
|
||||
t := make([]packet, s)
|
||||
copy(t, l.packets)
|
||||
l.packets = t
|
||||
}
|
||||
}
|
||||
|
||||
func (l *packetList) InsertSorted(pkt packet) {
|
||||
for i := range l.packets {
|
||||
if i >= l.slot {
|
||||
l.packets[i] = pkt
|
||||
l.slot++
|
||||
return
|
||||
}
|
||||
if pkt.hdr.sequenceNo == l.packets[i].hdr.sequenceNo {
|
||||
return
|
||||
}
|
||||
if pkt.Less(l.packets[i]) {
|
||||
copy(l.packets[i+1:], l.packets[i:])
|
||||
l.packets[i] = pkt
|
||||
if l.slot < len(l.packets) {
|
||||
l.slot++
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *packetList) LowestSeq() sequenceNo {
|
||||
return l.packets[0].hdr.sequenceNo
|
||||
}
|
||||
|
||||
func (l *packetList) PopSequence(maxSeq sequenceNo) []packet {
|
||||
highSeq := l.packets[0].hdr.sequenceNo
|
||||
if highSeq >= maxSeq {
|
||||
return nil
|
||||
}
|
||||
|
||||
var i int
|
||||
for i = 1; i < l.slot; i++ {
|
||||
seq := l.packets[i].hdr.sequenceNo
|
||||
if seq != highSeq+1 || seq >= maxSeq {
|
||||
break
|
||||
}
|
||||
highSeq++
|
||||
}
|
||||
pkts := make([]packet, i)
|
||||
copy(pkts, l.packets[:i])
|
||||
l.Cut(i)
|
||||
return pkts
|
||||
}
|
||||
155
core/dst/packets.go
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
const dstHeaderLen = 12
|
||||
|
||||
type packetType int8
|
||||
|
||||
const (
|
||||
typeHandshake packetType = 0x0
|
||||
typeData = 0x1
|
||||
typeAck = 0x2
|
||||
typeNegAck = 0x3
|
||||
typeShutdown = 0x4
|
||||
)
|
||||
|
||||
func (t packetType) String() string {
|
||||
switch t {
|
||||
case typeData:
|
||||
return "data"
|
||||
case typeHandshake:
|
||||
return "handshake"
|
||||
case typeAck:
|
||||
return "ack"
|
||||
case typeNegAck:
|
||||
return "negAck"
|
||||
case typeShutdown:
|
||||
return "shutdown"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type connectionID uint32
|
||||
|
||||
func (c connectionID) String() string {
|
||||
return fmt.Sprintf("Ci%08x", uint32(c))
|
||||
}
|
||||
|
||||
type sequenceNo uint32
|
||||
|
||||
func (s sequenceNo) String() string {
|
||||
return fmt.Sprintf("Sq%d", uint32(s))
|
||||
}
|
||||
|
||||
type timestamp uint32
|
||||
|
||||
func (t timestamp) String() string {
|
||||
return fmt.Sprintf("Ts%d", uint32(t))
|
||||
}
|
||||
|
||||
const (
|
||||
flagRequest = 1 << 0 // This packet is a handshake request
|
||||
flagResponse = 1 << 1 // This packet is a handshake response
|
||||
flagCookie = 1 << 2 // This packet contains a coookie challenge
|
||||
)
|
||||
|
||||
type header struct {
|
||||
packetType packetType // 4 bits
|
||||
flags uint8 // 4 bits
|
||||
connID connectionID // 24 bits
|
||||
sequenceNo sequenceNo
|
||||
timestamp timestamp
|
||||
}
|
||||
|
||||
func (h header) marshal(bs []byte) {
|
||||
binary.BigEndian.PutUint32(bs, uint32(h.connID&0xffffff))
|
||||
bs[0] = h.flags | uint8(h.packetType)<<4
|
||||
binary.BigEndian.PutUint32(bs[4:], uint32(h.sequenceNo))
|
||||
binary.BigEndian.PutUint32(bs[8:], uint32(h.timestamp))
|
||||
}
|
||||
|
||||
func unmarshalHeader(bs []byte) header {
|
||||
var h header
|
||||
h.packetType = packetType(bs[0] >> 4)
|
||||
h.flags = bs[0] & 0xf
|
||||
h.connID = connectionID(binary.BigEndian.Uint32(bs) & 0xffffff)
|
||||
h.sequenceNo = sequenceNo(binary.BigEndian.Uint32(bs[4:]))
|
||||
h.timestamp = timestamp(binary.BigEndian.Uint32(bs[8:]))
|
||||
return h
|
||||
}
|
||||
|
||||
func (h header) String() string {
|
||||
return fmt.Sprintf("header{type=%s flags=0x%x connID=%v seq=%v time=%v}", h.packetType, h.flags, h.connID, h.sequenceNo, h.timestamp)
|
||||
}
|
||||
|
||||
type handshakeData struct {
|
||||
packetSize uint32
|
||||
connID connectionID
|
||||
cookie uint32
|
||||
}
|
||||
|
||||
func (h handshakeData) marshalInto(data []byte) {
|
||||
binary.BigEndian.PutUint32(data[0:], h.packetSize)
|
||||
binary.BigEndian.PutUint32(data[4:], uint32(h.connID))
|
||||
binary.BigEndian.PutUint32(data[8:], h.cookie)
|
||||
}
|
||||
|
||||
func (h handshakeData) marshal() []byte {
|
||||
var data [12]byte
|
||||
h.marshalInto(data[:])
|
||||
return data[:]
|
||||
}
|
||||
|
||||
func unmarshalHandshakeData(data []byte) handshakeData {
|
||||
var h handshakeData
|
||||
h.packetSize = binary.BigEndian.Uint32(data[0:])
|
||||
h.connID = connectionID(binary.BigEndian.Uint32(data[4:]))
|
||||
h.cookie = binary.BigEndian.Uint32(data[8:])
|
||||
return h
|
||||
}
|
||||
|
||||
func (h handshakeData) String() string {
|
||||
return fmt.Sprintf("handshake{size=%d connID=%v cookie=0x%08x}", h.packetSize, h.connID, h.cookie)
|
||||
}
|
||||
|
||||
type packet struct {
|
||||
src connectionID
|
||||
dst net.Addr
|
||||
hdr header
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (p packet) String() string {
|
||||
var dst string
|
||||
if p.dst != nil {
|
||||
dst = "dst=" + p.dst.String() + " "
|
||||
}
|
||||
switch p.hdr.packetType {
|
||||
case typeHandshake:
|
||||
return fmt.Sprintf("%spacket{src=%v %v %v}", dst, p.src, p.hdr, unmarshalHandshakeData(p.data))
|
||||
default:
|
||||
return fmt.Sprintf("%spacket{src=%v %v data[:%d]}", dst, p.src, p.hdr, len(p.data))
|
||||
}
|
||||
}
|
||||
|
||||
func (p packet) LessSeq(seq sequenceNo) bool {
|
||||
diff := seq - p.hdr.sequenceNo
|
||||
if diff == 0 {
|
||||
return false
|
||||
}
|
||||
return diff < 1<<31
|
||||
}
|
||||
|
||||
func (a packet) Less(b packet) bool {
|
||||
return a.LessSeq(b.hdr.sequenceNo)
|
||||
}
|
||||
268
core/dst/sendbuffer.go
Normal file
@ -0,0 +1,268 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/juju/ratelimit"
|
||||
)
|
||||
|
||||
/*
|
||||
sendWindow
|
||||
v
|
||||
[S|S|S|S|Q|Q|Q|Q| | | | | | | | | ]
|
||||
^ ^writeSlot
|
||||
sendSlot
|
||||
*/
|
||||
type sendBuffer struct {
|
||||
mux *Mux // we send packets here
|
||||
scheduler *ratelimit.Bucket // sets send rate for packets
|
||||
|
||||
sendWindow int // maximum number of outstanding non-acked packets
|
||||
packetRate int // target pps
|
||||
|
||||
send packetList // buffered packets
|
||||
sendSlot int // buffer slot from which to send next packet
|
||||
|
||||
lost packetList // list of packets reported lost by timeout
|
||||
lostSlot int // next lost packet to resend
|
||||
|
||||
closed bool
|
||||
closing bool
|
||||
mut sync.Mutex
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
const (
|
||||
schedulerRate = 1e6
|
||||
schedulerCapacity = schedulerRate / 40
|
||||
)
|
||||
|
||||
// newSendBuffer creates a new send buffer with a zero window.
|
||||
// SetRateAndWindow() must be called to set an initial packet rate and send
|
||||
// window before using.
|
||||
func newSendBuffer(m *Mux) *sendBuffer {
|
||||
b := &sendBuffer{
|
||||
mux: m,
|
||||
scheduler: ratelimit.NewBucketWithRate(schedulerRate, schedulerCapacity),
|
||||
}
|
||||
b.cond = sync.NewCond(&b.mut)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
b.writerLoop()
|
||||
}()
|
||||
return b
|
||||
}
|
||||
|
||||
// Write puts a new packet in send buffer and schedules a send. Blocks when
|
||||
// the window size is or would be exceeded.
|
||||
func (b *sendBuffer) Write(pkt packet) error {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
for b.send.Full() || b.send.Len() >= b.sendWindow {
|
||||
if b.closing {
|
||||
return ErrClosedConn
|
||||
}
|
||||
if debugConnection {
|
||||
log.Println(b, "Write blocked")
|
||||
}
|
||||
b.cond.Wait()
|
||||
}
|
||||
if !b.send.Append(pkt) {
|
||||
panic("bug: append failed")
|
||||
}
|
||||
b.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Acknowledge removes packets with lower sequence numbers from the loss list
|
||||
// or send buffer.
|
||||
func (b *sendBuffer) Acknowledge(seq sequenceNo) {
|
||||
b.mut.Lock()
|
||||
|
||||
if cut := b.lost.CutLessSeq(seq); cut > 0 {
|
||||
if debugConnection {
|
||||
log.Println(b, "cut", cut, "from loss list")
|
||||
}
|
||||
// Next resend should always start with the first packet, regardless
|
||||
// of what we might already have resent previously.
|
||||
b.lostSlot = 0
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
|
||||
if cut := b.send.CutLessSeq(seq); cut > 0 {
|
||||
if debugConnection {
|
||||
log.Println(b, "cut", cut, "from send list")
|
||||
}
|
||||
b.sendSlot -= cut
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
func (b *sendBuffer) NegativeAck(seq sequenceNo) {
|
||||
b.mut.Lock()
|
||||
|
||||
pkts := b.send.PopSequence(seq)
|
||||
if cut := len(pkts); cut > 0 {
|
||||
b.lost.AppendAll(pkts)
|
||||
if debugConnection {
|
||||
log.Println(b, "cut", cut, "from send list, adding to loss list")
|
||||
log.Println(seq, pkts)
|
||||
}
|
||||
b.sendSlot -= cut
|
||||
b.lostSlot = 0
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
// ScheduleResend arranges for a resend of all currently unacknowledged
|
||||
// packets.
|
||||
func (b *sendBuffer) ScheduleResend() {
|
||||
b.mut.Lock()
|
||||
|
||||
if b.sendSlot > 0 {
|
||||
// There are packets that have been sent but not acked. Move them from
|
||||
// the send buffer to the loss list for retransmission.
|
||||
if debugConnection {
|
||||
log.Println(b, "scheduled resend from send list", b.sendSlot)
|
||||
}
|
||||
|
||||
// Append the packets to the loss list and rewind the send buffer
|
||||
b.lost.AppendAll(b.send.All()[:b.sendSlot])
|
||||
b.send.Cut(b.sendSlot)
|
||||
b.sendSlot = 0
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
|
||||
if b.lostSlot > 0 {
|
||||
// Also resend whatever was already in the loss list
|
||||
if debugConnection {
|
||||
log.Println(b, "scheduled resend from loss list", b.lostSlot)
|
||||
}
|
||||
b.lostSlot = 0
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
// SetWindowAndRate sets the window size (in packets) and packet rate (in
|
||||
// packets per second) to use when sending.
|
||||
func (b *sendBuffer) SetWindowAndRate(sendWindow, packetRate int) {
|
||||
b.mut.Lock()
|
||||
if debugConnection {
|
||||
log.Println(b, "new window & rate", sendWindow, packetRate)
|
||||
}
|
||||
b.packetRate = packetRate
|
||||
b.sendWindow = sendWindow
|
||||
if b.sendWindow > b.send.Cap() {
|
||||
b.send.Resize(b.sendWindow)
|
||||
b.cond.Broadcast()
|
||||
}
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
// Stop stops the send buffer from any doing further sending, but waits for
|
||||
// the current buffers to be drained.
|
||||
func (b *sendBuffer) Stop() {
|
||||
b.mut.Lock()
|
||||
|
||||
if b.closed || b.closing {
|
||||
return
|
||||
}
|
||||
|
||||
b.closing = true
|
||||
for b.lost.Len() > 0 || b.send.Len() > 0 {
|
||||
b.cond.Wait()
|
||||
}
|
||||
|
||||
b.closed = true
|
||||
b.cond.Broadcast()
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
// CrashStop stops the send buffer from any doing further sending, without
|
||||
// waiting for buffers to drain.
|
||||
func (b *sendBuffer) CrashStop() {
|
||||
b.mut.Lock()
|
||||
|
||||
if b.closed || b.closing {
|
||||
return
|
||||
}
|
||||
|
||||
b.closing = true
|
||||
b.closed = true
|
||||
b.cond.Broadcast()
|
||||
b.mut.Unlock()
|
||||
}
|
||||
|
||||
func (b *sendBuffer) String() string {
|
||||
return fmt.Sprintf("sendBuffer@%p", b)
|
||||
}
|
||||
|
||||
func (b *sendBuffer) writerLoop() {
|
||||
if debugConnection {
|
||||
log.Println(b, "writer() starting")
|
||||
defer log.Println(b, "writer() exiting")
|
||||
}
|
||||
|
||||
b.scheduler.Take(schedulerCapacity)
|
||||
for {
|
||||
var pkt packet
|
||||
b.mut.Lock()
|
||||
for b.lostSlot >= b.sendWindow ||
|
||||
(b.sendSlot == b.send.Len() && b.lostSlot == b.lost.Len()) {
|
||||
if b.closed {
|
||||
b.mut.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if debugConnection {
|
||||
log.Println(b, "writer() paused", b.lostSlot, b.sendSlot, b.sendWindow, b.lost.Len())
|
||||
}
|
||||
b.cond.Wait()
|
||||
}
|
||||
|
||||
if b.lostSlot < b.lost.Len() {
|
||||
pkt = b.lost.All()[b.lostSlot]
|
||||
pkt.hdr.timestamp = timestampMicros()
|
||||
b.lostSlot++
|
||||
|
||||
if debugConnection {
|
||||
log.Println(b, "resend", b.lostSlot, b.lost.Len(), b.sendWindow, pkt.hdr.connID, pkt.hdr.sequenceNo)
|
||||
}
|
||||
} else if b.sendSlot < b.send.Len() {
|
||||
pkt = b.send.All()[b.sendSlot]
|
||||
pkt.hdr.timestamp = timestampMicros()
|
||||
b.sendSlot++
|
||||
|
||||
if debugConnection {
|
||||
log.Println(b, "send", b.sendSlot, b.send.Len(), b.sendWindow, pkt.hdr.connID, pkt.hdr.sequenceNo)
|
||||
}
|
||||
}
|
||||
|
||||
b.cond.Broadcast()
|
||||
packetRate := b.packetRate
|
||||
b.mut.Unlock()
|
||||
|
||||
if pkt.dst != nil {
|
||||
b.scheduler.Wait(schedulerRate / int64(packetRate))
|
||||
b.mux.write(pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
core/dst/util.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
logger "log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.New(os.Stderr, "", logger.LstdFlags)
|
||||
|
||||
func SetLogger(l *logger.Logger) {
|
||||
log = l
|
||||
}
|
||||
func timestampMicros() timestamp {
|
||||
return timestamp(time.Now().UnixNano() / 1000)
|
||||
}
|
||||
|
||||
func randomSeqNo() sequenceNo {
|
||||
return sequenceNo(rand.Uint32())
|
||||
}
|
||||
|
||||
func randomConnID() connectionID {
|
||||
return connectionID(rand.Uint32() & 0xffffff)
|
||||
}
|
||||
144
core/dst/windowcc.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2014 The DST Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type windowCC struct {
|
||||
minWindow int
|
||||
maxWindow int
|
||||
currentWindow int
|
||||
minRate int
|
||||
maxRate int
|
||||
currentRate int
|
||||
targetRate int
|
||||
|
||||
curRTT time.Duration
|
||||
minRTT time.Duration
|
||||
|
||||
statsFile io.WriteCloser
|
||||
start time.Time
|
||||
}
|
||||
|
||||
func newWindowCC() *windowCC {
|
||||
var statsFile io.WriteCloser
|
||||
|
||||
if debugCC {
|
||||
statsFile, _ = os.Create(fmt.Sprintf("cc-log-%d.csv", time.Now().Unix()))
|
||||
fmt.Fprintf(statsFile, "ms,minWin,maxWin,curWin,minRate,maxRate,curRate,minRTT,curRTT\n")
|
||||
}
|
||||
|
||||
return &windowCC{
|
||||
minWindow: 1, // Packets
|
||||
maxWindow: 16 << 10,
|
||||
currentWindow: 1,
|
||||
|
||||
minRate: 100, // PPS
|
||||
maxRate: 80e3, // Roughly 1 Gbps at 1500 bytes per packet
|
||||
currentRate: 100,
|
||||
targetRate: 1000,
|
||||
|
||||
minRTT: 10 * time.Second,
|
||||
statsFile: statsFile,
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowCC) Ack() {
|
||||
if w.curRTT > w.minRTT+100*time.Millisecond {
|
||||
return
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
if w.currentWindow < w.maxWindow {
|
||||
w.currentWindow++
|
||||
changed = true
|
||||
}
|
||||
|
||||
if w.currentRate != w.targetRate {
|
||||
w.currentRate = (w.currentRate*7 + w.targetRate) / 8
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed && debugCC {
|
||||
w.log()
|
||||
log.Println("Ack", w.currentWindow, w.currentRate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowCC) NegAck() {
|
||||
if w.currentWindow > w.minWindow {
|
||||
w.currentWindow /= 2
|
||||
}
|
||||
if w.currentRate > w.minRate {
|
||||
w.currentRate /= 2
|
||||
}
|
||||
if debugCC {
|
||||
w.log()
|
||||
log.Println("NegAck", w.currentWindow, w.currentRate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowCC) Exp() {
|
||||
w.currentWindow = w.minWindow
|
||||
if debugCC {
|
||||
w.log()
|
||||
log.Println("Exp", w.currentWindow, w.currentRate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowCC) SendWindow() int {
|
||||
if w.currentWindow < w.minWindow {
|
||||
return w.minWindow
|
||||
}
|
||||
if w.currentWindow > w.maxWindow {
|
||||
return w.maxWindow
|
||||
}
|
||||
return w.currentWindow
|
||||
}
|
||||
|
||||
func (w *windowCC) PacketRate() int {
|
||||
if w.currentRate < w.minRate {
|
||||
return w.minRate
|
||||
}
|
||||
if w.currentRate > w.maxRate {
|
||||
return w.maxRate
|
||||
}
|
||||
return w.currentRate
|
||||
}
|
||||
|
||||
func (w *windowCC) UpdateRTT(rtt time.Duration) {
|
||||
w.curRTT = rtt
|
||||
if w.curRTT < w.minRTT {
|
||||
w.minRTT = w.curRTT
|
||||
if debugCC {
|
||||
log.Println("Min RTT", w.minRTT)
|
||||
}
|
||||
}
|
||||
|
||||
if w.curRTT > w.minRTT+200*time.Millisecond && w.targetRate > 2*w.minRate {
|
||||
w.targetRate -= w.minRate
|
||||
} else if w.curRTT < w.minRTT+20*time.Millisecond && w.targetRate < w.maxRate {
|
||||
w.targetRate += w.minRate
|
||||
}
|
||||
|
||||
if debugCC {
|
||||
w.log()
|
||||
log.Println("RTT", w.curRTT, "target rate", w.targetRate, "current rate", w.currentRate, "current window", w.currentWindow)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowCC) log() {
|
||||
if w.statsFile == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w.statsFile, "%.02f,%d,%d,%d,%d,%d,%d,%.02f,%.02f\n", time.Since(w.start).Seconds()*1000, w.minWindow, w.maxWindow, w.currentWindow, w.minRate, w.maxRate, w.currentRate, w.minRTT.Seconds()*1000, w.curRTT.Seconds()*1000)
|
||||
}
|
||||
52
core/lib/buf/leakybuf.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Provides leaky buffer, based on the example in Effective Go.
|
||||
package buf
|
||||
|
||||
type LeakyBuf struct {
|
||||
bufSize int // size of each buffer
|
||||
freeList chan []byte
|
||||
}
|
||||
|
||||
const LeakyBufSize = 2048 // data.len(2) + hmacsha1(10) + data(4096)
|
||||
const maxNBuf = 2048
|
||||
|
||||
var LeakyBuffer = NewLeakyBuf(maxNBuf, LeakyBufSize)
|
||||
|
||||
func Get() (b []byte) {
|
||||
return LeakyBuffer.Get()
|
||||
}
|
||||
func Put(b []byte) {
|
||||
LeakyBuffer.Put(b)
|
||||
}
|
||||
|
||||
// NewLeakyBuf creates a leaky buffer which can hold at most n buffer, each
|
||||
// with bufSize bytes.
|
||||
func NewLeakyBuf(n, bufSize int) *LeakyBuf {
|
||||
return &LeakyBuf{
|
||||
bufSize: bufSize,
|
||||
freeList: make(chan []byte, n),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a buffer from the leaky buffer or create a new buffer.
|
||||
func (lb *LeakyBuf) Get() (b []byte) {
|
||||
select {
|
||||
case b = <-lb.freeList:
|
||||
default:
|
||||
b = make([]byte, lb.bufSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Put add the buffer into the free buffer pool for reuse. Panic if the buffer
|
||||
// size is not the same with the leaky buffer's. This is intended to expose
|
||||
// error usage of leaky buffer.
|
||||
func (lb *LeakyBuf) Put(b []byte) {
|
||||
if len(b) != lb.bufSize {
|
||||
panic("invalid buffer size that's put into leaky buffer")
|
||||
}
|
||||
select {
|
||||
case lb.freeList <- b:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
68
core/lib/ioutils/utils.go
Normal file
@ -0,0 +1,68 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
logger "log"
|
||||
|
||||
lbuf "github.com/snail007/goproxy/core/lib/buf"
|
||||
)
|
||||
|
||||
func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interface{}), log *logger.Logger) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("bind crashed %s", err)
|
||||
}
|
||||
}()
|
||||
e1 := make(chan interface{}, 1)
|
||||
e2 := make(chan interface{}, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("bind crashed %s", err)
|
||||
}
|
||||
}()
|
||||
//_, err := io.Copy(dst, src)
|
||||
err := ioCopy(dst, src)
|
||||
e1 <- err
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("bind crashed %s", err)
|
||||
}
|
||||
}()
|
||||
//_, err := io.Copy(src, dst)
|
||||
err := ioCopy(src, dst)
|
||||
e2 <- err
|
||||
}()
|
||||
var err interface{}
|
||||
select {
|
||||
case err = <-e1:
|
||||
//log.Printf("e1")
|
||||
case err = <-e2:
|
||||
//log.Printf("e2")
|
||||
}
|
||||
src.Close()
|
||||
dst.Close()
|
||||
if fn != nil {
|
||||
fn(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
|
||||
buf := lbuf.LeakyBuffer.Get()
|
||||
defer lbuf.LeakyBuffer.Put(buf)
|
||||
n := 0
|
||||
for {
|
||||
n, err = src.Read(buf)
|
||||
if n > 0 {
|
||||
if _, e := dst.Write(buf[0:n]); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
24
core/lib/kcpcfg/args.go
Normal file
@ -0,0 +1,24 @@
|
||||
package kcpcfg
|
||||
|
||||
import kcp "github.com/xtaci/kcp-go"
|
||||
|
||||
type KCPConfigArgs struct {
|
||||
Key *string
|
||||
Crypt *string
|
||||
Mode *string
|
||||
MTU *int
|
||||
SndWnd *int
|
||||
RcvWnd *int
|
||||
DataShard *int
|
||||
ParityShard *int
|
||||
DSCP *int
|
||||
NoComp *bool
|
||||
AckNodelay *bool
|
||||
NoDelay *int
|
||||
Interval *int
|
||||
Resend *int
|
||||
NoCongestion *int
|
||||
SockBuf *int
|
||||
KeepAlive *int
|
||||
Block kcp.BlockCrypt
|
||||
}
|
||||
355
core/lib/mapx/map.go
Normal file
@ -0,0 +1,355 @@
|
||||
package mapx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SHARD_COUNT = 32
|
||||
|
||||
// A "thread" safe map of type string:Anything.
|
||||
// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards.
|
||||
type ConcurrentMap []*ConcurrentMapShared
|
||||
|
||||
// A "thread" safe string to anything map.
|
||||
type ConcurrentMapShared struct {
|
||||
items map[string]interface{}
|
||||
sync.RWMutex // Read Write mutex, guards access to internal map.
|
||||
}
|
||||
|
||||
// Creates a new concurrent map.
|
||||
func NewConcurrentMap() ConcurrentMap {
|
||||
m := make(ConcurrentMap, SHARD_COUNT)
|
||||
for i := 0; i < SHARD_COUNT; i++ {
|
||||
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Returns shard under given key
|
||||
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
|
||||
return m[uint(fnv32(key))%uint(SHARD_COUNT)]
|
||||
}
|
||||
|
||||
func (m ConcurrentMap) MSet(data map[string]interface{}) {
|
||||
for key, value := range data {
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
shard.items[key] = value
|
||||
shard.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the given value under the specified key.
|
||||
func (m ConcurrentMap) Set(key string, value interface{}) {
|
||||
// Get map shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
shard.items[key] = value
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
// Callback to return new element to be inserted into the map
|
||||
// It is called while lock is held, therefore it MUST NOT
|
||||
// try to access other keys in same map, as it can lead to deadlock since
|
||||
// Go sync.RWLock is not reentrant
|
||||
type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{}
|
||||
|
||||
// Insert or Update - updates existing element or inserts a new one using UpsertCb
|
||||
func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) {
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
v, ok := shard.items[key]
|
||||
res = cb(ok, v, value)
|
||||
shard.items[key] = res
|
||||
shard.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// Sets the given value under the specified key if no value was associated with it.
|
||||
func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
|
||||
// Get map shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
_, ok := shard.items[key]
|
||||
if !ok {
|
||||
shard.items[key] = value
|
||||
}
|
||||
shard.Unlock()
|
||||
return !ok
|
||||
}
|
||||
|
||||
// Retrieves an element from map under given key.
|
||||
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
|
||||
// Get shard
|
||||
shard := m.GetShard(key)
|
||||
shard.RLock()
|
||||
// Get item from shard.
|
||||
val, ok := shard.items[key]
|
||||
shard.RUnlock()
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Returns the number of elements within the map.
|
||||
func (m ConcurrentMap) Count() int {
|
||||
count := 0
|
||||
for i := 0; i < SHARD_COUNT; i++ {
|
||||
shard := m[i]
|
||||
shard.RLock()
|
||||
count += len(shard.items)
|
||||
shard.RUnlock()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Looks up an item under specified key
|
||||
func (m ConcurrentMap) Has(key string) bool {
|
||||
// Get shard
|
||||
shard := m.GetShard(key)
|
||||
shard.RLock()
|
||||
// See if element is within shard.
|
||||
_, ok := shard.items[key]
|
||||
shard.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Removes an element from the map.
|
||||
func (m ConcurrentMap) Remove(key string) {
|
||||
// Try to get shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
delete(shard.items, key)
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
// Removes an element from the map and returns it
|
||||
func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
|
||||
// Try to get shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
v, exists = shard.items[key]
|
||||
delete(shard.items, key)
|
||||
shard.Unlock()
|
||||
return v, exists
|
||||
}
|
||||
|
||||
// Checks if map is empty.
|
||||
func (m ConcurrentMap) IsEmpty() bool {
|
||||
return m.Count() == 0
|
||||
}
|
||||
|
||||
// Used by the Iter & IterBuffered functions to wrap two variables together over a channel,
|
||||
type Tuple struct {
|
||||
Key string
|
||||
Val interface{}
|
||||
}
|
||||
|
||||
// Returns an iterator which could be used in a for range loop.
|
||||
//
|
||||
// Deprecated: using IterBuffered() will get a better performence
|
||||
func (m ConcurrentMap) Iter() <-chan Tuple {
|
||||
chans := snapshot(m)
|
||||
ch := make(chan Tuple)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fanIn(chans, ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Returns a buffered iterator which could be used in a for range loop.
|
||||
func (m ConcurrentMap) IterBuffered() <-chan Tuple {
|
||||
chans := snapshot(m)
|
||||
total := 0
|
||||
for _, c := range chans {
|
||||
total += cap(c)
|
||||
}
|
||||
ch := make(chan Tuple, total)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fanIn(chans, ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Returns a array of channels that contains elements in each shard,
|
||||
// which likely takes a snapshot of `m`.
|
||||
// It returns once the size of each buffered channel is determined,
|
||||
// before all the channels are populated using goroutines.
|
||||
func snapshot(m ConcurrentMap) (chans []chan Tuple) {
|
||||
chans = make([]chan Tuple, SHARD_COUNT)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(SHARD_COUNT)
|
||||
// Foreach shard.
|
||||
for index, shard := range m {
|
||||
go func(index int, shard *ConcurrentMapShared) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
// Foreach key, value pair.
|
||||
shard.RLock()
|
||||
chans[index] = make(chan Tuple, len(shard.items))
|
||||
wg.Done()
|
||||
for key, val := range shard.items {
|
||||
chans[index] <- Tuple{key, val}
|
||||
}
|
||||
shard.RUnlock()
|
||||
close(chans[index])
|
||||
}(index, shard)
|
||||
}
|
||||
wg.Wait()
|
||||
return chans
|
||||
}
|
||||
|
||||
// fanIn reads elements from channels `chans` into channel `out`
|
||||
func fanIn(chans []chan Tuple, out chan Tuple) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(chans))
|
||||
for _, ch := range chans {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
func(ch chan Tuple) {
|
||||
for t := range ch {
|
||||
out <- t
|
||||
}
|
||||
wg.Done()
|
||||
}(ch)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(out)
|
||||
}
|
||||
|
||||
// Returns all items as map[string]interface{}
|
||||
func (m ConcurrentMap) Items() map[string]interface{} {
|
||||
tmp := make(map[string]interface{})
|
||||
|
||||
// Insert items to temporary map.
|
||||
for item := range m.IterBuffered() {
|
||||
tmp[item.Key] = item.Val
|
||||
}
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
// Iterator callback,called for every key,value found in
|
||||
// maps. RLock is held for all calls for a given shard
|
||||
// therefore callback sess consistent view of a shard,
|
||||
// but not across the shards
|
||||
type IterCb func(key string, v interface{})
|
||||
|
||||
// Callback based iterator, cheapest way to read
|
||||
// all elements in a map.
|
||||
func (m ConcurrentMap) IterCb(fn IterCb) {
|
||||
for idx := range m {
|
||||
shard := (m)[idx]
|
||||
shard.RLock()
|
||||
for key, value := range shard.items {
|
||||
fn(key, value)
|
||||
}
|
||||
shard.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Return all keys as []string
|
||||
func (m ConcurrentMap) Keys() []string {
|
||||
count := m.Count()
|
||||
ch := make(chan string, count)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
// Foreach shard.
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(SHARD_COUNT)
|
||||
for _, shard := range m {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
func(shard *ConcurrentMapShared) {
|
||||
// Foreach key, value pair.
|
||||
shard.RLock()
|
||||
for key := range shard.items {
|
||||
ch <- key
|
||||
}
|
||||
shard.RUnlock()
|
||||
wg.Done()
|
||||
}(shard)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Generate keys
|
||||
keys := make([]string, 0, count)
|
||||
for k := range ch {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
//Reviles ConcurrentMap "private" variables to json marshal.
|
||||
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
|
||||
// Create a temporary map, which will hold all item spread across shards.
|
||||
tmp := make(map[string]interface{})
|
||||
|
||||
// Insert items to temporary map.
|
||||
for item := range m.IterBuffered() {
|
||||
tmp[item.Key] = item.Val
|
||||
}
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
func fnv32(key string) uint32 {
|
||||
hash := uint32(2166136261)
|
||||
const prime32 = uint32(16777619)
|
||||
for i := 0; i < len(key); i++ {
|
||||
hash *= prime32
|
||||
hash ^= uint32(key[i])
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// Concurrent map uses Interface{} as its value, therefor JSON Unmarshal
|
||||
// will probably won't know which to type to unmarshal into, in such case
|
||||
// we'll end up with a value of type map[string]interface{}, In most cases this isn't
|
||||
// out value type, this is why we've decided to remove this functionality.
|
||||
|
||||
// func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) {
|
||||
// // Reverse process of Marshal.
|
||||
|
||||
// tmp := make(map[string]interface{})
|
||||
|
||||
// // Unmarshal into a single map.
|
||||
// if err := json.Unmarshal(b, &tmp); err != nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // foreach key,value pair in temporary map insert into our concurrent map.
|
||||
// for key, val := range tmp {
|
||||
// m.Set(key, val)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
159
core/lib/socks5/socks5.go
Normal file
@ -0,0 +1,159 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
Method_NO_AUTH = uint8(0x00)
|
||||
Method_GSSAPI = uint8(0x01)
|
||||
Method_USER_PASS = uint8(0x02)
|
||||
Method_IANA = uint8(0x7F)
|
||||
Method_RESVERVE = uint8(0x80)
|
||||
Method_NONE_ACCEPTABLE = uint8(0xFF)
|
||||
VERSION_V5 = uint8(0x05)
|
||||
CMD_CONNECT = uint8(0x01)
|
||||
CMD_BIND = uint8(0x02)
|
||||
CMD_ASSOCIATE = uint8(0x03)
|
||||
ATYP_IPV4 = uint8(0x01)
|
||||
ATYP_DOMAIN = uint8(0x03)
|
||||
ATYP_IPV6 = uint8(0x04)
|
||||
REP_SUCCESS = uint8(0x00)
|
||||
REP_REQ_FAIL = uint8(0x01)
|
||||
REP_RULE_FORBIDDEN = uint8(0x02)
|
||||
REP_NETWOR_UNREACHABLE = uint8(0x03)
|
||||
REP_HOST_UNREACHABLE = uint8(0x04)
|
||||
REP_CONNECTION_REFUSED = uint8(0x05)
|
||||
REP_TTL_TIMEOUT = uint8(0x06)
|
||||
REP_CMD_UNSUPPORTED = uint8(0x07)
|
||||
REP_ATYP_UNSUPPORTED = uint8(0x08)
|
||||
REP_UNKNOWN = uint8(0x09)
|
||||
RSV = uint8(0x00)
|
||||
)
|
||||
|
||||
var (
|
||||
ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
ZERO_PORT = []byte{0x00, 0x00}
|
||||
)
|
||||
var Socks5Errors = []string{
|
||||
"",
|
||||
"general failure",
|
||||
"connection forbidden",
|
||||
"network unreachable",
|
||||
"host unreachable",
|
||||
"connection refused",
|
||||
"TTL expired",
|
||||
"command not supported",
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
// Auth contains authentication parameters that specific Dialers may require.
|
||||
type UsernamePassword struct {
|
||||
Username, Password string
|
||||
}
|
||||
|
||||
type PacketUDP struct {
|
||||
rsv uint16
|
||||
frag uint8
|
||||
atype uint8
|
||||
dstHost string
|
||||
dstPort string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func NewPacketUDP() (p PacketUDP) {
|
||||
return PacketUDP{}
|
||||
}
|
||||
func (p *PacketUDP) Build(destAddr string, data []byte) (err error) {
|
||||
host, port, err := net.SplitHostPort(destAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.rsv = 0
|
||||
p.frag = 0
|
||||
p.dstHost = host
|
||||
p.dstPort = port
|
||||
p.atype = ATYP_IPV4
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
p.atype = ATYP_IPV4
|
||||
ip = ip4
|
||||
} else {
|
||||
p.atype = ATYP_IPV6
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
err = errors.New("proxy: destination host name too long: " + host)
|
||||
return
|
||||
}
|
||||
p.atype = ATYP_DOMAIN
|
||||
}
|
||||
p.data = data
|
||||
|
||||
return
|
||||
}
|
||||
func (p *PacketUDP) Parse(b []byte) (err error) {
|
||||
p.frag = uint8(b[2])
|
||||
if p.frag != 0 {
|
||||
err = fmt.Errorf("FRAG only support for 0 , %v ,%v", p.frag, b[:4])
|
||||
return
|
||||
}
|
||||
portIndex := 0
|
||||
p.atype = b[3]
|
||||
switch p.atype {
|
||||
case ATYP_IPV4: //IP V4
|
||||
p.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String()
|
||||
portIndex = 8
|
||||
case ATYP_DOMAIN: //域名
|
||||
domainLen := uint8(b[4])
|
||||
p.dstHost = string(b[5 : 5+domainLen]) //b[4]表示域名的长度
|
||||
portIndex = int(5 + domainLen)
|
||||
case ATYP_IPV6: //IP V6
|
||||
p.dstHost = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()
|
||||
portIndex = 20
|
||||
}
|
||||
p.dstPort = strconv.Itoa(int(b[portIndex])<<8 | int(b[portIndex+1]))
|
||||
p.data = b[portIndex+2:]
|
||||
return
|
||||
}
|
||||
func (p *PacketUDP) Header() []byte {
|
||||
header := new(bytes.Buffer)
|
||||
header.Write([]byte{0x00, 0x00, p.frag, p.atype})
|
||||
if p.atype == ATYP_IPV4 {
|
||||
ip := net.ParseIP(p.dstHost)
|
||||
header.Write(ip.To4())
|
||||
} else if p.atype == ATYP_IPV6 {
|
||||
ip := net.ParseIP(p.dstHost)
|
||||
header.Write(ip.To16())
|
||||
} else if p.atype == ATYP_DOMAIN {
|
||||
hBytes := []byte(p.dstHost)
|
||||
header.WriteByte(byte(len(hBytes)))
|
||||
header.Write(hBytes)
|
||||
}
|
||||
port, _ := strconv.ParseUint(p.dstPort, 10, 64)
|
||||
portBytes := new(bytes.Buffer)
|
||||
binary.Write(portBytes, binary.BigEndian, port)
|
||||
header.Write(portBytes.Bytes()[portBytes.Len()-2:])
|
||||
return header.Bytes()
|
||||
}
|
||||
func (p *PacketUDP) Bytes() []byte {
|
||||
packBytes := new(bytes.Buffer)
|
||||
packBytes.Write(p.Header())
|
||||
packBytes.Write(p.data)
|
||||
return packBytes.Bytes()
|
||||
}
|
||||
func (p *PacketUDP) Host() string {
|
||||
return p.dstHost
|
||||
}
|
||||
|
||||
func (p *PacketUDP) Port() string {
|
||||
return p.dstPort
|
||||
}
|
||||
func (p *PacketUDP) Data() []byte {
|
||||
return p.data
|
||||
}
|
||||
62
core/lib/transport/compress.go
Normal file
@ -0,0 +1,62 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
func NewCompStream(conn net.Conn) *CompStream {
|
||||
c := new(CompStream)
|
||||
c.conn = conn
|
||||
c.w = snappy.NewBufferedWriter(conn)
|
||||
c.r = snappy.NewReader(conn)
|
||||
return c
|
||||
}
|
||||
func NewCompConn(conn net.Conn) net.Conn {
|
||||
c := CompStream{}
|
||||
c.conn = conn
|
||||
c.w = snappy.NewBufferedWriter(conn)
|
||||
c.r = snappy.NewReader(conn)
|
||||
return &c
|
||||
}
|
||||
|
||||
type CompStream struct {
|
||||
net.Conn
|
||||
conn net.Conn
|
||||
w *snappy.Writer
|
||||
r *snappy.Reader
|
||||
}
|
||||
|
||||
func (c *CompStream) Read(p []byte) (n int, err error) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
func (c *CompStream) Write(p []byte) (n int, err error) {
|
||||
n, err = c.w.Write(p)
|
||||
err = c.w.Flush()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *CompStream) Close() (err error) {
|
||||
err = c.conn.Close()
|
||||
c.r = nil
|
||||
c.w = nil
|
||||
return
|
||||
}
|
||||
func (c *CompStream) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
func (c *CompStream) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
func (c *CompStream) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
func (c *CompStream) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
func (c *CompStream) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
56
core/lib/transport/encrypt/conn.go
Normal file
@ -0,0 +1,56 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
lbuf "github.com/snail007/goproxy/core/lib/buf"
|
||||
)
|
||||
|
||||
var (
|
||||
lBuf = lbuf.NewLeakyBuf(2048, 2048)
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
*Cipher
|
||||
w io.Writer
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, method, password string) (conn net.Conn, err error) {
|
||||
cipher0, err := NewCipher(method, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn = &Conn{
|
||||
Conn: c,
|
||||
Cipher: cipher0,
|
||||
r: &cipher.StreamReader{S: cipher0.ReadStream, R: c},
|
||||
w: &cipher.StreamWriter{S: cipher0.WriteStream, W: c},
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *Conn) Read(b []byte) (n int, err error) {
|
||||
if s.r == nil {
|
||||
return 0, fmt.Errorf("use of closed network connection")
|
||||
}
|
||||
return s.r.Read(b)
|
||||
}
|
||||
func (s *Conn) Write(b []byte) (n int, err error) {
|
||||
if s.w == nil {
|
||||
return 0, fmt.Errorf("use of closed network connection")
|
||||
}
|
||||
return s.w.Write(b)
|
||||
}
|
||||
func (s *Conn) Close() (err error) {
|
||||
if s.Cipher != nil {
|
||||
err = s.Conn.Close()
|
||||
s.Cipher = nil
|
||||
s.r = nil
|
||||
s.w = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
185
core/lib/transport/encrypt/encrypt.go
Normal file
@ -0,0 +1,185 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/md5"
|
||||
"crypto/rc4"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
|
||||
lbuf "github.com/snail007/goproxy/core/lib/buf"
|
||||
"github.com/Yawning/chacha20"
|
||||
"golang.org/x/crypto/blowfish"
|
||||
"golang.org/x/crypto/cast5"
|
||||
)
|
||||
|
||||
const leakyBufSize = 2048
|
||||
const maxNBuf = 2048
|
||||
|
||||
var leakyBuf = lbuf.NewLeakyBuf(maxNBuf, leakyBufSize)
|
||||
var errEmptyPassword = errors.New("proxy key")
|
||||
|
||||
func md5sum(d []byte) []byte {
|
||||
h := md5.New()
|
||||
h.Write(d)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func evpBytesToKey(password string, keyLen int) (key []byte) {
|
||||
const md5Len = 16
|
||||
cnt := (keyLen-1)/md5Len + 1
|
||||
m := make([]byte, cnt*md5Len)
|
||||
copy(m, md5sum([]byte(password)))
|
||||
|
||||
// Repeatedly call md5 until bytes generated is enough.
|
||||
// Each call to md5 uses data: prev md5 sum + password.
|
||||
d := make([]byte, md5Len+len(password))
|
||||
start := 0
|
||||
for i := 1; i < cnt; i++ {
|
||||
start += md5Len
|
||||
copy(d, m[start-md5Len:start])
|
||||
copy(d[md5Len:], password)
|
||||
copy(m[start:], md5sum(d))
|
||||
}
|
||||
return m[:keyLen]
|
||||
}
|
||||
|
||||
type DecOrEnc int
|
||||
|
||||
const (
|
||||
Decrypt DecOrEnc = iota
|
||||
Encrypt
|
||||
)
|
||||
|
||||
func newStream(block cipher.Block, err error, key, iv []byte,
|
||||
doe DecOrEnc) (cipher.Stream, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if doe == Encrypt {
|
||||
return cipher.NewCFBEncrypter(block, iv), nil
|
||||
} else {
|
||||
return cipher.NewCFBDecrypter(block, iv), nil
|
||||
}
|
||||
}
|
||||
|
||||
func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
return newStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(block, iv), nil
|
||||
}
|
||||
|
||||
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := des.NewCipher(key)
|
||||
return newStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := blowfish.NewCipher(key)
|
||||
return newStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := cast5.NewCipher(key)
|
||||
return newStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
h := md5.New()
|
||||
h.Write(key)
|
||||
h.Write(iv)
|
||||
rc4key := h.Sum(nil)
|
||||
|
||||
return rc4.NewCipher(rc4key)
|
||||
}
|
||||
|
||||
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
return chacha20.NewCipher(key, iv)
|
||||
}
|
||||
|
||||
func newChaCha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
return chacha20.NewCipher(key, iv)
|
||||
}
|
||||
|
||||
type cipherInfo struct {
|
||||
keyLen int
|
||||
ivLen int
|
||||
newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
var cipherMethod = map[string]*cipherInfo{
|
||||
"aes-128-cfb": {16, 16, newAESCFBStream},
|
||||
"aes-192-cfb": {24, 16, newAESCFBStream},
|
||||
"aes-256-cfb": {32, 16, newAESCFBStream},
|
||||
"aes-128-ctr": {16, 16, newAESCTRStream},
|
||||
"aes-192-ctr": {24, 16, newAESCTRStream},
|
||||
"aes-256-ctr": {32, 16, newAESCTRStream},
|
||||
"des-cfb": {8, 8, newDESStream},
|
||||
"bf-cfb": {16, 8, newBlowFishStream},
|
||||
"cast5-cfb": {16, 8, newCast5Stream},
|
||||
"rc4-md5": {16, 16, newRC4MD5Stream},
|
||||
"rc4-md5-6": {16, 6, newRC4MD5Stream},
|
||||
"chacha20": {32, 8, newChaCha20Stream},
|
||||
"chacha20-ietf": {32, 12, newChaCha20IETFStream},
|
||||
}
|
||||
|
||||
func GetCipherMethods() (keys []string) {
|
||||
keys = []string{}
|
||||
for k := range cipherMethod {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return
|
||||
}
|
||||
func CheckCipherMethod(method string) error {
|
||||
if method == "" {
|
||||
method = "aes-256-cfb"
|
||||
}
|
||||
_, ok := cipherMethod[method]
|
||||
if !ok {
|
||||
return errors.New("Unsupported encryption method: " + method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Cipher struct {
|
||||
WriteStream cipher.Stream
|
||||
ReadStream cipher.Stream
|
||||
key []byte
|
||||
info *cipherInfo
|
||||
}
|
||||
|
||||
func NewCipher(method, password string) (c *Cipher, err error) {
|
||||
if password == "" {
|
||||
return nil, errEmptyPassword
|
||||
}
|
||||
mi, ok := cipherMethod[method]
|
||||
if !ok {
|
||||
return nil, errors.New("Unsupported encryption method: " + method)
|
||||
}
|
||||
key := evpBytesToKey(password, mi.keyLen)
|
||||
c = &Cipher{key: key, info: mi}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//hash(key) -> read IV
|
||||
riv := sha256.New().Sum(c.key)[:c.info.ivLen]
|
||||
c.ReadStream, err = c.info.newStream(c.key, riv, Decrypt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} //hash(read IV) -> write IV
|
||||
wiv := sha256.New().Sum(riv)[:c.info.ivLen]
|
||||
c.WriteStream, err = c.info.newStream(c.key, wiv, Encrypt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
234
core/lib/udp/udp.go
Normal file
@ -0,0 +1,234 @@
|
||||
package udputils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
logger "log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
bufx "github.com/snail007/goproxy/core/lib/buf"
|
||||
mapx "github.com/snail007/goproxy/core/lib/mapx"
|
||||
)
|
||||
|
||||
type CreateOutUDPConnFn func(listener *net.UDPConn, srcAddr *net.UDPAddr, packet []byte) (outconn *net.UDPConn, err error)
|
||||
type CleanFn func(srcAddr string)
|
||||
type BeforeSendFn func(listener *net.UDPConn, srcAddr *net.UDPAddr, b []byte) (sendB []byte, err error)
|
||||
type BeforeReplyFn func(listener *net.UDPConn, srcAddr *net.UDPAddr, outconn *net.UDPConn, b []byte) (replyB []byte, err error)
|
||||
|
||||
type IOBinder struct {
|
||||
outConns mapx.ConcurrentMap
|
||||
listener *net.UDPConn
|
||||
createOutUDPConnFn CreateOutUDPConnFn
|
||||
log *logger.Logger
|
||||
timeout time.Duration
|
||||
cleanFn CleanFn
|
||||
inTCPConn *net.Conn
|
||||
outTCPConn *net.Conn
|
||||
beforeSendFn BeforeSendFn
|
||||
beforeReplyFn BeforeReplyFn
|
||||
}
|
||||
|
||||
func NewIOBinder(listener *net.UDPConn, log *logger.Logger) *IOBinder {
|
||||
return &IOBinder{
|
||||
listener: listener,
|
||||
outConns: mapx.NewConcurrentMap(),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
func (s *IOBinder) Factory(fn CreateOutUDPConnFn) *IOBinder {
|
||||
s.createOutUDPConnFn = fn
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) AfterReadFromClient(fn BeforeSendFn) *IOBinder {
|
||||
s.beforeSendFn = fn
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) AfterReadFromServer(fn BeforeReplyFn) *IOBinder {
|
||||
s.beforeReplyFn = fn
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) Timeout(timeout time.Duration) *IOBinder {
|
||||
s.timeout = timeout
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) Clean(fn CleanFn) *IOBinder {
|
||||
s.cleanFn = fn
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) AliveWithServeConn(srcAddr string, inTCPConn *net.Conn) *IOBinder {
|
||||
s.inTCPConn = inTCPConn
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
buf := make([]byte, 1)
|
||||
(*inTCPConn).SetReadDeadline(time.Time{})
|
||||
if _, err := (*inTCPConn).Read(buf); err != nil {
|
||||
s.log.Printf("udp related tcp conn of client disconnected with read , %s", err.Error())
|
||||
s.clean(srcAddr)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
(*inTCPConn).SetWriteDeadline(time.Now().Add(time.Second * 5))
|
||||
if _, err := (*inTCPConn).Write([]byte{0x00}); err != nil {
|
||||
s.log.Printf("udp related tcp conn of client disconnected with write , %s", err.Error())
|
||||
s.clean(srcAddr)
|
||||
return
|
||||
}
|
||||
(*inTCPConn).SetWriteDeadline(time.Time{})
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) AliveWithClientConn(srcAddr string, outTCPConn *net.Conn) *IOBinder {
|
||||
s.outTCPConn = outTCPConn
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
buf := make([]byte, 1)
|
||||
(*outTCPConn).SetReadDeadline(time.Time{})
|
||||
if _, err := (*outTCPConn).Read(buf); err != nil {
|
||||
s.log.Printf("udp related tcp conn to parent disconnected with read , %s", err.Error())
|
||||
s.clean(srcAddr)
|
||||
}
|
||||
}()
|
||||
return s
|
||||
}
|
||||
func (s *IOBinder) Run() (err error) {
|
||||
var (
|
||||
isClosedErr = func(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "use of closed network connection")
|
||||
}
|
||||
isTimeoutErr = func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
e, ok := err.(net.Error)
|
||||
return ok && e.Timeout()
|
||||
}
|
||||
isRefusedErr = func(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "connection refused")
|
||||
}
|
||||
)
|
||||
for {
|
||||
buf := bufx.Get()
|
||||
defer bufx.Put(buf)
|
||||
n, srcAddr, err := s.listener.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
s.log.Printf("read from client error %s", err)
|
||||
if isClosedErr(err) {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
var data []byte
|
||||
if s.beforeSendFn != nil {
|
||||
data, err = s.beforeSendFn(s.listener, srcAddr, buf[:n])
|
||||
if err != nil {
|
||||
s.log.Printf("beforeSend retured an error , %s", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
data = buf[:n]
|
||||
}
|
||||
inconnRemoteAddr := srcAddr.String()
|
||||
var outconn *net.UDPConn
|
||||
if v, ok := s.outConns.Get(inconnRemoteAddr); !ok {
|
||||
outconn, err = s.createOutUDPConnFn(s.listener, srcAddr, data)
|
||||
if err != nil {
|
||||
s.log.Printf("connnect fail %s", err)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s",e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
s.clean(srcAddr.String())
|
||||
}()
|
||||
buf := bufx.Get()
|
||||
defer bufx.Put(buf)
|
||||
for {
|
||||
if s.timeout > 0 {
|
||||
outconn.SetReadDeadline(time.Now().Add(s.timeout))
|
||||
}
|
||||
n, srcAddr, err := outconn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
s.log.Printf("read from remote error %s", err)
|
||||
if isClosedErr(err) || isTimeoutErr(err) || isRefusedErr(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
data := buf[:n]
|
||||
if s.beforeReplyFn != nil {
|
||||
data, err = s.beforeReplyFn(s.listener, srcAddr, outconn, buf[:n])
|
||||
if err != nil {
|
||||
s.log.Printf("beforeReply retured an error , %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
_, err = s.listener.WriteTo(data, srcAddr)
|
||||
if err != nil {
|
||||
s.log.Printf("write to remote error %s", err)
|
||||
if isClosedErr(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
outconn = v.(*net.UDPConn)
|
||||
}
|
||||
|
||||
s.log.Printf("use decrpyted data , %v", data)
|
||||
|
||||
_, err = outconn.Write(data)
|
||||
|
||||
if err != nil {
|
||||
s.log.Printf("write to remote error %s", err)
|
||||
if isClosedErr(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *IOBinder) clean(srcAddr string) *IOBinder {
|
||||
if v, ok := s.outConns.Get(srcAddr); ok {
|
||||
(*v.(*net.UDPConn)).Close()
|
||||
s.outConns.Remove(srcAddr)
|
||||
}
|
||||
if s.inTCPConn != nil {
|
||||
(*s.inTCPConn).Close()
|
||||
}
|
||||
if s.outTCPConn != nil {
|
||||
(*s.outTCPConn).Close()
|
||||
}
|
||||
if s.cleanFn != nil {
|
||||
s.cleanFn(srcAddr)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IOBinder) Close() {
|
||||
for _, c := range s.outConns.Items() {
|
||||
(*c.(*net.UDPConn)).Close()
|
||||
}
|
||||
}
|
||||
31
core/proxy/client/proxy.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Package proxy provides support for a variety of protocols to proxy network
|
||||
// data.
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
socks5c "github.com/snail007/goproxy/core/lib/socks5"
|
||||
socks5 "github.com/snail007/goproxy/core/proxy/client/socks5"
|
||||
)
|
||||
|
||||
// A Dialer is a means to establish a connection.
|
||||
type Dialer interface {
|
||||
// Dial connects to the given address via the proxy.
|
||||
DialConn(conn *net.Conn, network, addr string) (err error)
|
||||
}
|
||||
|
||||
// Auth contains authentication parameters that specific Dialers may require.
|
||||
type Auth struct {
|
||||
User, Password string
|
||||
}
|
||||
|
||||
func SOCKS5(timeout time.Duration, auth *Auth) (Dialer, error) {
|
||||
var a *socks5c.UsernamePassword
|
||||
if auth != nil {
|
||||
a = &socks5c.UsernamePassword{auth.User, auth.Password}
|
||||
}
|
||||
d := socks5.NewDialer(a, timeout)
|
||||
return d, nil
|
||||
}
|
||||
263
core/proxy/client/socks5/socks5.go
Normal file
@ -0,0 +1,263 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
socks5c "github.com/snail007/goproxy/core/lib/socks5"
|
||||
)
|
||||
|
||||
type Dialer struct {
|
||||
timeout time.Duration
|
||||
usernamePassword *socks5c.UsernamePassword
|
||||
}
|
||||
|
||||
// NewDialer returns a new Dialer that dials through the provided
|
||||
// proxy server's network and address.
|
||||
func NewDialer(auth *socks5c.UsernamePassword, timeout time.Duration) *Dialer {
|
||||
if auth != nil && auth.Password == "" && auth.Username == "" {
|
||||
auth = nil
|
||||
}
|
||||
return &Dialer{
|
||||
usernamePassword: auth,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dialer) DialConn(conn *net.Conn, network, addr string) (err error) {
|
||||
client := NewClientConn(conn, network, addr, d.timeout, d.usernamePassword, nil)
|
||||
err = client._Handshake()
|
||||
return
|
||||
}
|
||||
|
||||
type ClientConn struct {
|
||||
user string
|
||||
password string
|
||||
conn *net.Conn
|
||||
header []byte
|
||||
timeout time.Duration
|
||||
addr string
|
||||
network string
|
||||
udpAddr string
|
||||
}
|
||||
|
||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||
// target must be a canonical address with a host and port.
|
||||
// network : tcp udp
|
||||
func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration, auth *socks5c.UsernamePassword, header []byte) *ClientConn {
|
||||
s := &ClientConn{
|
||||
conn: conn,
|
||||
network: network,
|
||||
timeout: timeout,
|
||||
}
|
||||
if auth != nil {
|
||||
s.user = auth.Username
|
||||
s.password = auth.Password
|
||||
}
|
||||
if header != nil && len(header) > 0 {
|
||||
s.header = header
|
||||
}
|
||||
if network == "udp" && target == "" {
|
||||
target = "0.0.0.0:1"
|
||||
}
|
||||
s.addr = target
|
||||
return s
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *ClientConn) _Handshake() error {
|
||||
host, portStr, err := net.SplitHostPort(s.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
if err := s.auth(host); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := []byte{}
|
||||
if s.network == "tcp" {
|
||||
buf = append(buf, socks5c.VERSION_V5, socks5c.CMD_CONNECT, 0 /* reserved */)
|
||||
|
||||
} else {
|
||||
buf = append(buf, socks5c.VERSION_V5, socks5c.CMD_ASSOCIATE, 0 /* reserved */)
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
buf = append(buf, socks5c.ATYP_IPV4)
|
||||
ip = ip4
|
||||
} else {
|
||||
buf = append(buf, socks5c.ATYP_IPV6)
|
||||
}
|
||||
buf = append(buf, ip...)
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return errors.New("proxy: destination host name too long: " + host)
|
||||
}
|
||||
buf = append(buf, socks5c.ATYP_DOMAIN)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := (*s.conn).Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := io.ReadFull((*s.conn), buf[:4]); err != nil {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(socks5c.Socks5Errors) {
|
||||
failure = socks5c.Socks5Errors[buf[1]]
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case socks5c.ATYP_IPV4:
|
||||
bytesToDiscard = net.IPv4len
|
||||
case socks5c.ATYP_IPV6:
|
||||
bytesToDiscard = net.IPv6len
|
||||
case socks5c.ATYP_DOMAIN:
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
_, err := io.ReadFull((*s.conn), buf[:1])
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := io.ReadFull((*s.conn), buf); err != nil {
|
||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
var ip net.IP
|
||||
ip = buf
|
||||
ipStr := ""
|
||||
if bytesToDiscard == net.IPv4len || bytesToDiscard == net.IPv6len {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ipStr = ipv4.String()
|
||||
} else {
|
||||
ipStr = ip.To16().String()
|
||||
}
|
||||
}
|
||||
//log.Printf("%v", ipStr)
|
||||
// Also need to discard the port number
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
p := binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
|
||||
//log.Printf("%v", p)
|
||||
s.udpAddr = net.JoinHostPort(ipStr, fmt.Sprintf("%d", p))
|
||||
//log.Printf("%v", s.udpAddr)
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
return nil
|
||||
}
|
||||
func (s *ClientConn) SendUDP(data []byte, addr string) (respData []byte, err error) {
|
||||
|
||||
c, err := net.DialTimeout("udp", s.udpAddr, s.timeout)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn := c.(*net.UDPConn)
|
||||
|
||||
p := socks5c.NewPacketUDP()
|
||||
p.Build(addr, data)
|
||||
conn.SetDeadline(time.Now().Add(s.timeout))
|
||||
conn.Write(p.Bytes())
|
||||
conn.SetDeadline(time.Time{})
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
conn.SetDeadline(time.Now().Add(s.timeout))
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
conn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
respData = buf[:n]
|
||||
return
|
||||
}
|
||||
func (s *ClientConn) auth(host string) error {
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, socks5c.VERSION_V5)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2 /* num auth methods */, socks5c.Method_NO_AUTH, socks5c.Method_USER_PASS)
|
||||
} else {
|
||||
buf = append(buf, 1 /* num auth methods */, socks5c.Method_NO_AUTH)
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := (*s.conn).Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
// See RFC 1929
|
||||
if buf[1] == socks5c.Method_USER_PASS {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, 1 /* password protocol version */)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := (*s.conn).Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
if buf[1] != 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
79
core/proxy/client/tests/proxy_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
proxyclient "github.com/snail007/goproxy/core/proxy/client"
|
||||
sdk "github.com/snail007/goproxy/sdk/android-ios"
|
||||
)
|
||||
|
||||
func TestSocks5(t *testing.T) {
|
||||
estr := sdk.Start("s1", "socks -p :8185 --log test.log")
|
||||
if estr != "" {
|
||||
t.Fatal(estr)
|
||||
}
|
||||
p, e := proxyclient.SOCKS5(time.Second, nil)
|
||||
if e != nil {
|
||||
t.Error(e)
|
||||
} else {
|
||||
c, e := net.Dial("tcp", "127.0.0.1:8185")
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
e = p.DialConn(&c, "tcp", "www.baidu.com:80")
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
_, e = c.Write([]byte("Get / http/1.1\r\nHost: www.baidu.com\r\n"))
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
b, e := ioutil.ReadAll(c)
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if !strings.HasPrefix(string(b), "HTTP") {
|
||||
t.Fatalf("request baidu fail:%s", string(b))
|
||||
}
|
||||
}
|
||||
sdk.Stop("s1")
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestSocks5Auth(t *testing.T) {
|
||||
estr := sdk.Start("s1", "socks -p :8185 -a u:p --log test.log")
|
||||
if estr != "" {
|
||||
t.Fatal(estr)
|
||||
}
|
||||
p, e := proxyclient.SOCKS5(time.Second, &proxyclient.Auth{User: "u", Password: "p"})
|
||||
if e != nil {
|
||||
t.Error(e)
|
||||
} else {
|
||||
c, e := net.Dial("tcp", "127.0.0.1:8185")
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
e = p.DialConn(&c, "tcp", "www.baidu.com:80")
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
_, e = c.Write([]byte("Get / http/1.1\r\nHost: www.baidu.com\r\n"))
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
b, e := ioutil.ReadAll(c)
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if !strings.HasPrefix(string(b), "HTTP") {
|
||||
t.Fatalf("request baidu fail:%s", string(b))
|
||||
}
|
||||
}
|
||||
sdk.Stop("s1")
|
||||
os.Remove("test.log")
|
||||
}
|
||||
375
core/proxy/server/socks5/server.go
Normal file
@ -0,0 +1,375 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
socks5c "github.com/snail007/goproxy/core/lib/socks5"
|
||||
)
|
||||
|
||||
type BasicAuther interface {
|
||||
CheckUserPass(username, password, userIP, localIP, toTarget string) bool
|
||||
}
|
||||
type Request struct {
|
||||
ver uint8
|
||||
cmd uint8
|
||||
reserve uint8
|
||||
addressType uint8
|
||||
dstAddr string
|
||||
dstPort string
|
||||
dstHost string
|
||||
bytes []byte
|
||||
rw io.ReadWriter
|
||||
}
|
||||
|
||||
func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{}) {
|
||||
var b = make([]byte, 1024)
|
||||
var n int
|
||||
req = Request{rw: rw}
|
||||
if header != nil && len(header) == 1 && len(header[0]) > 1 {
|
||||
b = header[0]
|
||||
n = len(header[0])
|
||||
} else {
|
||||
n, err = rw.Read(b[:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read req data fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
req.ver = uint8(b[0])
|
||||
req.cmd = uint8(b[1])
|
||||
req.reserve = uint8(b[2])
|
||||
req.addressType = uint8(b[3])
|
||||
if b[0] != 0x5 {
|
||||
err = fmt.Errorf("sosck version supported")
|
||||
req.TCPReply(socks5c.REP_REQ_FAIL)
|
||||
return
|
||||
}
|
||||
switch b[3] {
|
||||
case 0x01: //IP V4
|
||||
req.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String()
|
||||
case 0x03: //域名
|
||||
req.dstHost = string(b[5 : n-2]) //b[4]表示域名的长度
|
||||
case 0x04: //IP V6
|
||||
req.dstHost = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()
|
||||
}
|
||||
req.dstPort = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1]))
|
||||
req.dstAddr = net.JoinHostPort(req.dstHost, req.dstPort)
|
||||
req.bytes = b[:n]
|
||||
return
|
||||
}
|
||||
func (s *Request) Bytes() []byte {
|
||||
return s.bytes
|
||||
}
|
||||
func (s *Request) Addr() string {
|
||||
return s.dstAddr
|
||||
}
|
||||
func (s *Request) Host() string {
|
||||
return s.dstHost
|
||||
}
|
||||
func (s *Request) Port() string {
|
||||
return s.dstPort
|
||||
}
|
||||
func (s *Request) AType() uint8 {
|
||||
return s.addressType
|
||||
}
|
||||
func (s *Request) CMD() uint8 {
|
||||
return s.cmd
|
||||
}
|
||||
|
||||
func (s *Request) TCPReply(rep uint8) (err error) {
|
||||
_, err = s.rw.Write(s.NewReply(rep, "0.0.0.0:0"))
|
||||
return
|
||||
}
|
||||
func (s *Request) UDPReply(rep uint8, addr string) (err error) {
|
||||
_, err = s.rw.Write(s.NewReply(rep, addr))
|
||||
return
|
||||
}
|
||||
func (s *Request) NewReply(rep uint8, addr string) []byte {
|
||||
var response bytes.Buffer
|
||||
host, port, _ := net.SplitHostPort(addr)
|
||||
ip := net.ParseIP(host)
|
||||
ipb := ip.To4()
|
||||
atyp := socks5c.ATYP_IPV4
|
||||
ipv6 := ip.To16()
|
||||
zeroiIPv6 := fmt.Sprintf("%d%d%d%d%d%d%d%d%d%d%d%d",
|
||||
ipv6[0], ipv6[1], ipv6[2], ipv6[3],
|
||||
ipv6[4], ipv6[5], ipv6[6], ipv6[7],
|
||||
ipv6[8], ipv6[9], ipv6[10], ipv6[11],
|
||||
)
|
||||
if ipb == nil && ipv6 != nil && "0000000000255255" != zeroiIPv6 {
|
||||
atyp = socks5c.ATYP_IPV6
|
||||
ipb = ip.To16()
|
||||
}
|
||||
porti, _ := strconv.Atoi(port)
|
||||
portb := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portb, uint16(porti))
|
||||
// log.Printf("atyp : %v", atyp)
|
||||
// log.Printf("ip : %v", []byte(ip))
|
||||
response.WriteByte(socks5c.VERSION_V5)
|
||||
response.WriteByte(rep)
|
||||
response.WriteByte(socks5c.RSV)
|
||||
response.WriteByte(atyp)
|
||||
response.Write(ipb)
|
||||
response.Write(portb)
|
||||
return response.Bytes()
|
||||
}
|
||||
|
||||
type MethodsRequest struct {
|
||||
ver uint8
|
||||
methodsCount uint8
|
||||
methods []uint8
|
||||
bytes []byte
|
||||
rw *io.ReadWriter
|
||||
}
|
||||
|
||||
func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err interface{}) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = recover()
|
||||
}
|
||||
}()
|
||||
s = MethodsRequest{}
|
||||
s.rw = &r
|
||||
var buf = make([]byte, 300)
|
||||
var n int
|
||||
if header != nil && len(header) == 1 && len(header[0]) > 1 {
|
||||
buf = header[0]
|
||||
n = len(header[0])
|
||||
} else {
|
||||
n, err = r.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if buf[0] != 0x05 {
|
||||
err = fmt.Errorf("socks version not supported")
|
||||
return
|
||||
}
|
||||
if n != int(buf[1])+int(2) {
|
||||
err = fmt.Errorf("socks methods data length error")
|
||||
return
|
||||
}
|
||||
s.ver = buf[0]
|
||||
s.methodsCount = buf[1]
|
||||
s.methods = buf[2:n]
|
||||
s.bytes = buf[:n]
|
||||
return
|
||||
}
|
||||
func (s *MethodsRequest) Version() uint8 {
|
||||
return s.ver
|
||||
}
|
||||
func (s *MethodsRequest) MethodsCount() uint8 {
|
||||
return s.methodsCount
|
||||
}
|
||||
func (s *MethodsRequest) Methods() []uint8 {
|
||||
return s.methods
|
||||
}
|
||||
func (s *MethodsRequest) Select(method uint8) bool {
|
||||
for _, m := range s.methods {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s *MethodsRequest) Reply(method uint8) (err error) {
|
||||
_, err = (*s.rw).Write([]byte{byte(socks5c.VERSION_V5), byte(method)})
|
||||
return
|
||||
}
|
||||
func (s *MethodsRequest) Bytes() []byte {
|
||||
return s.bytes
|
||||
}
|
||||
|
||||
type ServerConn struct {
|
||||
target string
|
||||
user string
|
||||
password string
|
||||
conn *net.Conn
|
||||
timeout time.Duration
|
||||
auth *BasicAuther
|
||||
header []byte
|
||||
ver uint8
|
||||
//method
|
||||
methodsCount uint8
|
||||
methods []uint8
|
||||
method uint8
|
||||
//request
|
||||
cmd uint8
|
||||
reserve uint8
|
||||
addressType uint8
|
||||
dstAddr string
|
||||
dstPort string
|
||||
dstHost string
|
||||
udpAddress string
|
||||
}
|
||||
|
||||
func NewServerConn(conn *net.Conn, timeout time.Duration, auth *BasicAuther, udpAddress string, header []byte) *ServerConn {
|
||||
if udpAddress == "" {
|
||||
udpAddress = "0.0.0.0:16666"
|
||||
}
|
||||
s := &ServerConn{
|
||||
conn: conn,
|
||||
timeout: timeout,
|
||||
auth: auth,
|
||||
header: header,
|
||||
ver: socks5c.VERSION_V5,
|
||||
udpAddress: udpAddress,
|
||||
}
|
||||
return s
|
||||
|
||||
}
|
||||
func (s *ServerConn) Close() {
|
||||
(*s.conn).Close()
|
||||
}
|
||||
func (s *ServerConn) AuthData() socks5c.UsernamePassword {
|
||||
return socks5c.UsernamePassword{s.user, s.password}
|
||||
}
|
||||
func (s *ServerConn) Method() uint8 {
|
||||
return s.method
|
||||
}
|
||||
func (s *ServerConn) Target() string {
|
||||
return s.target
|
||||
}
|
||||
func (s *ServerConn) Handshake() (err error) {
|
||||
remoteAddr := (*s.conn).RemoteAddr()
|
||||
localAddr := (*s.conn).LocalAddr()
|
||||
//协商开始
|
||||
//method select request
|
||||
var methodReq MethodsRequest
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
|
||||
methodReq, e := NewMethodsRequest((*s.conn), s.header)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
if e != nil {
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
methodReq.Reply(socks5c.Method_NONE_ACCEPTABLE)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
err = fmt.Errorf("new methods request fail,ERR: %s", e)
|
||||
return
|
||||
}
|
||||
//log.Printf("%v,s.auth == %v && methodReq.Select(Method_NO_AUTH) %v", methodReq.methods, s.auth, methodReq.Select(Method_NO_AUTH))
|
||||
if s.auth == nil && methodReq.Select(socks5c.Method_NO_AUTH) && !methodReq.Select(socks5c.Method_USER_PASS) {
|
||||
// if !methodReq.Select(Method_NO_AUTH) {
|
||||
// (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||
// methodReq.Reply(Method_NONE_ACCEPTABLE)
|
||||
// (*s.conn).SetReadDeadline(time.Time{})
|
||||
// err = fmt.Errorf("none method found : Method_NO_AUTH")
|
||||
// return
|
||||
// }
|
||||
s.method = socks5c.Method_NO_AUTH
|
||||
//method select reply
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
err = methodReq.Reply(socks5c.Method_NO_AUTH)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("reply answer data fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
// err = fmt.Errorf("% x", methodReq.Bytes())
|
||||
} else {
|
||||
//auth
|
||||
if !methodReq.Select(socks5c.Method_USER_PASS) {
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
methodReq.Reply(socks5c.Method_NONE_ACCEPTABLE)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
err = fmt.Errorf("none method found : Method_USER_PASS")
|
||||
return
|
||||
}
|
||||
s.method = socks5c.Method_USER_PASS
|
||||
//method reply need auth
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
err = methodReq.Reply(socks5c.Method_USER_PASS)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("reply answer data fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
//read auth
|
||||
buf := make([]byte, 500)
|
||||
var n int
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
n, err = (*s.conn).Read(buf)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read auth info fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
r := buf[:n]
|
||||
s.user = string(r[2 : r[1]+2])
|
||||
s.password = string(r[2+r[1]+1:])
|
||||
//err = fmt.Errorf("user:%s,pass:%s", user, pass)
|
||||
//auth
|
||||
_userAddr := strings.Split(remoteAddr.String(), ":")
|
||||
_localAddr := strings.Split(localAddr.String(), ":")
|
||||
if s.auth == nil || (*s.auth).CheckUserPass(s.user, s.password, _userAddr[0], _localAddr[0], "") {
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
_, err = (*s.conn).Write([]byte{0x01, 0x00})
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("answer auth success to %s fail,ERR: %s", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||
_, err = (*s.conn).Write([]byte{0x01, 0x01})
|
||||
(*s.conn).SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("answer auth fail to %s fail,ERR: %s", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("auth fail from %s", remoteAddr)
|
||||
return
|
||||
}
|
||||
}
|
||||
//request detail
|
||||
(*s.conn).SetReadDeadline(time.Now().Add(s.timeout))
|
||||
request, e := NewRequest(*s.conn)
|
||||
(*s.conn).SetReadDeadline(time.Time{})
|
||||
if e != nil {
|
||||
err = fmt.Errorf("read request data fail,ERR: %s", e)
|
||||
return
|
||||
}
|
||||
//协商结束
|
||||
|
||||
switch request.CMD() {
|
||||
case socks5c.CMD_BIND:
|
||||
err = request.TCPReply(socks5c.REP_UNKNOWN)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TCPReply REP_UNKNOWN to %s fail,ERR: %s", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("cmd bind not supported, form: %s", remoteAddr)
|
||||
return
|
||||
case socks5c.CMD_CONNECT:
|
||||
err = request.TCPReply(socks5c.REP_SUCCESS)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TCPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
case socks5c.CMD_ASSOCIATE:
|
||||
err = request.UDPReply(socks5c.REP_SUCCESS, s.udpAddress)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("UDPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//fill socks info
|
||||
s.target = request.Addr()
|
||||
s.methodsCount = methodReq.MethodsCount()
|
||||
s.methods = methodReq.Methods()
|
||||
s.cmd = request.CMD()
|
||||
s.reserve = request.reserve
|
||||
s.addressType = request.addressType
|
||||
s.dstAddr = request.dstAddr
|
||||
s.dstHost = request.dstHost
|
||||
s.dstPort = request.dstPort
|
||||
return
|
||||
}
|
||||
35
core/tproxy/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Pass-through user IP manual
|
||||
|
||||
## Description:
|
||||
|
||||
By Linux TPROXY function,you can achieve the source Station service program can see the client's real IP, to achieve this feature requires linux operating systems and programs must meet certain conditions.
|
||||
|
||||
## Environmental requirements:
|
||||
|
||||
The source station must be a service program running on Linux, and Linux needs to meet the following conditions:
|
||||
|
||||
1. Linux kernel version >= 2.6.28
|
||||
|
||||
2. Determine whether the system supports TPROXY, execute:
|
||||
|
||||
grep TPROXY /boot/config-`uname -r`
|
||||
|
||||
If the output has the following result description is supported.
|
||||
|
||||
CONFIG_NETFILTER_XT_TARGET_TPROXY=m
|
||||
|
||||
## Deployment steps:
|
||||
|
||||
1. The tproxy environment setup script should be executed with root privileges every time the boot from the source Linux system: tproxy_setup.sh
|
||||
|
||||
2. Execute proxy proxy with root access on the source Linux system
|
||||
|
||||
## Parameter-tproxy is the tproxy function that turns on the proxy.
|
||||
|
||||
./proxy -tproxy
|
||||
|
||||
2. The IP address of the source station to listen to the program requires the use of: `127.0.1.1`
|
||||
|
||||
For example, the address of the source station before listening is: `0.0.0.0:8800`, now need to be modified to: `127.0.1.1:8800`
|
||||
|
||||
3. Forwarding rules inside the source address must be the corresponding, such as the above: `127.0.1.1:8800`
|
||||
35
core/tproxy/README_ZH.md
Normal file
@ -0,0 +1,35 @@
|
||||
# 透传用户IP手册
|
||||
|
||||
## 说明:
|
||||
|
||||
通过Linux的TPROXY功能,可以实现源站服务程序可以看见客户端真实IP,实现该功能需要linux操作系统和程序都要满足一定的条件.
|
||||
|
||||
## 环境要求:
|
||||
|
||||
源站必须是运行在Linux上面的服务程序,同时Linux需要满足下面条件:
|
||||
|
||||
1. Linux内核版本 >= 2.6.28
|
||||
|
||||
2. 判断系统是否支持TPROXY,执行:
|
||||
|
||||
grep TPROXY /boot/config-`uname -r`
|
||||
|
||||
如果输出有下面的结果说明支持.
|
||||
|
||||
CONFIG_NETFILTER_XT_TARGET_TPROXY=m
|
||||
|
||||
## 部署步骤:
|
||||
|
||||
1. 在源站的linux系统里面每次开机启动都要用root权限执行tproxy环境设置脚本:tproxy_setup.sh
|
||||
|
||||
2. 在源站的linux系统里面使用root权限执行代理proxy
|
||||
|
||||
## 参数 -tproxy 是开启代理的tproxy功能.
|
||||
|
||||
./proxy -tproxy
|
||||
|
||||
2. 源站的程序监听的地址IP需要使用:127.0.1.1
|
||||
|
||||
比如源站以前监听的地址是: 0.0.0.0:8800 , 现在需要修改为:127.0.1.1:8800
|
||||
|
||||
3. 转发规则里面源站地址必须是对应的,比如上面的:127.0.1.1:8800
|
||||
249
core/tproxy/tproxy.go
Normal file
@ -0,0 +1,249 @@
|
||||
// Package tproxy provides the TCPDial and TCPListen tproxy equivalent of the
|
||||
// net package Dial and Listen with tproxy support for linux ONLY.
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const big = 0xFFFFFF
|
||||
const IP_ORIGADDRS = 20
|
||||
|
||||
// Debug outs the library in Debug mode
|
||||
var Debug = false
|
||||
|
||||
func ipToSocksAddr(family int, ip net.IP, port int, zone string) (unix.Sockaddr, error) {
|
||||
switch family {
|
||||
case unix.AF_INET:
|
||||
if len(ip) == 0 {
|
||||
ip = net.IPv4zero
|
||||
}
|
||||
if ip = ip.To4(); ip == nil {
|
||||
return nil, net.InvalidAddrError("non-IPv4 address")
|
||||
}
|
||||
sa := new(unix.SockaddrInet4)
|
||||
for i := 0; i < net.IPv4len; i++ {
|
||||
sa.Addr[i] = ip[i]
|
||||
}
|
||||
sa.Port = port
|
||||
return sa, nil
|
||||
case unix.AF_INET6:
|
||||
if len(ip) == 0 {
|
||||
ip = net.IPv6zero
|
||||
}
|
||||
// IPv4 callers use 0.0.0.0 to mean "announce on any available address".
|
||||
// In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0",
|
||||
// which it refuses to do. Rewrite to the IPv6 unspecified address.
|
||||
if ip.Equal(net.IPv4zero) {
|
||||
ip = net.IPv6zero
|
||||
}
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return nil, net.InvalidAddrError("non-IPv6 address")
|
||||
}
|
||||
sa := new(unix.SockaddrInet6)
|
||||
for i := 0; i < net.IPv6len; i++ {
|
||||
sa.Addr[i] = ip[i]
|
||||
}
|
||||
sa.Port = port
|
||||
sa.ZoneId = uint32(zoneToInt(zone))
|
||||
return sa, nil
|
||||
}
|
||||
return nil, net.InvalidAddrError("unexpected socket family")
|
||||
}
|
||||
|
||||
func zoneToInt(zone string) int {
|
||||
if zone == "" {
|
||||
return 0
|
||||
}
|
||||
if ifi, err := net.InterfaceByName(zone); err == nil {
|
||||
return ifi.Index
|
||||
}
|
||||
n, _, _ := dtoi(zone, 0)
|
||||
return n
|
||||
}
|
||||
|
||||
func dtoi(s string, i0 int) (n int, i int, ok bool) {
|
||||
n = 0
|
||||
for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
|
||||
n = n*10 + int(s[i]-'0')
|
||||
if n >= big {
|
||||
return 0, i, false
|
||||
}
|
||||
}
|
||||
if i == i0 {
|
||||
return 0, i, false
|
||||
}
|
||||
return n, i, true
|
||||
}
|
||||
|
||||
// IPTcpAddrToUnixSocksAddr returns Sockaddr for specified TCP addr.
|
||||
func IPTcpAddrToUnixSocksAddr(addr string) (sa unix.Sockaddr, err error) {
|
||||
if Debug {
|
||||
fmt.Println("DEBUG: IPTcpAddrToUnixSocksAddr recieved address:", addr)
|
||||
}
|
||||
addressNet := "tcp6"
|
||||
if addr[0] != '[' {
|
||||
addressNet = "tcp4"
|
||||
}
|
||||
tcpAddr, err := net.ResolveTCPAddr(addressNet, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipToSocksAddr(ipType(addr), tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone)
|
||||
}
|
||||
|
||||
// IPv6UdpAddrToUnixSocksAddr returns Sockaddr for specified IPv6 addr.
|
||||
func IPv6UdpAddrToUnixSocksAddr(addr string) (sa unix.Sockaddr, err error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("udp6", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipToSocksAddr(unix.AF_INET6, tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone)
|
||||
}
|
||||
|
||||
// TCPListen is listening for incoming IP packets which are being intercepted.
|
||||
// In conflict to regular Listen mehtod the socket destination and source addresses
|
||||
// are of the intercepted connection.
|
||||
// Else then that it works exactly like net package net.Listen.
|
||||
func TCPListen(listenAddr string) (listener net.Listener, err error) {
|
||||
s, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer unix.Close(s)
|
||||
err = unix.SetsockoptInt(s, unix.SOL_IP, unix.IP_TRANSPARENT, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sa, err := IPTcpAddrToUnixSocksAddr(listenAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unix.Bind(s, sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unix.Listen(s, unix.SOMAXCONN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := os.NewFile(uintptr(s), "TProxy")
|
||||
defer f.Close()
|
||||
return net.FileListener(f)
|
||||
}
|
||||
func ipType(localAddr string) int {
|
||||
host, _, _ := net.SplitHostPort(localAddr)
|
||||
if host != "" {
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil || ip.To4() != nil {
|
||||
return unix.AF_INET
|
||||
}
|
||||
return unix.AF_INET6
|
||||
}
|
||||
return unix.AF_INET
|
||||
}
|
||||
|
||||
// TCPDial is a special tcp connection which binds a non local address as the source.
|
||||
// Except then the option to bind to a specific local address which the machine doesn't posses
|
||||
// it is exactly like any other net.Conn connection.
|
||||
// It is advised to use port numbered 0 in the localAddr and leave the kernel to choose which
|
||||
// Local port to use in order to avoid errors and binding conflicts.
|
||||
func TCPDial(localAddr, remoteAddr string, timeout time.Duration) (conn net.Conn, err error) {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
if Debug {
|
||||
fmt.Println("TCPDial from:", localAddr, "to:", remoteAddr)
|
||||
}
|
||||
s, err := unix.Socket(ipType(localAddr), unix.SOCK_STREAM, 0)
|
||||
|
||||
//In a case there was a need for a non-blocking socket an example
|
||||
//s, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM |unix.SOCK_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
defer unix.Close(s)
|
||||
err = unix.SetsockoptInt(s, unix.SOL_IP, unix.IP_TRANSPARENT, 1)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR setting the socket in IP_TRANSPARENT mode", err)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
err = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR setting the socket in unix.SO_REUSEADDR mode", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rhost, _, err := net.SplitHostPort(localAddr)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
// fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Println("ERROR", err, "running net.SplitHostPort on address:", localAddr)
|
||||
}
|
||||
}
|
||||
|
||||
sa, err := IPTcpAddrToUnixSocksAddr(rhost + ":0")
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR creating a hostaddres for the socker with IPTcpAddrToUnixSocksAddr", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteSocket, err := IPTcpAddrToUnixSocksAddr(remoteAddr)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR creating a remoteSocket for the socker with IPTcpAddrToUnixSocksAddr on the remote addres", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.Bind(s, sa)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errChn := make(chan error, 1)
|
||||
func() {
|
||||
err = unix.Connect(s, remoteSocket)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR Connecting from", s, "to:", remoteSocket, "ERROR:", err)
|
||||
}
|
||||
}
|
||||
errChn <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-errChn:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-timer.C:
|
||||
return nil, fmt.Errorf("ERROR connect to %s timeout", remoteAddr)
|
||||
}
|
||||
f := os.NewFile(uintptr(s), "TProxyTCPClient")
|
||||
client, err := net.FileConn(f)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
fmt.Println("ERROR os.NewFile", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if Debug {
|
||||
fmt.Println("FINISHED Creating net.coo from:", client.LocalAddr().String(), "to:", client.RemoteAddr().String())
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
30
core/tproxy/tproxy_setup.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
SOURCE_BIND_IP="127.0.1.1"
|
||||
|
||||
echo 0 > /proc/sys/net/ipv4/conf/lo/rp_filter
|
||||
echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter
|
||||
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
|
||||
echo 1 > /proc/sys/net/ipv4/conf/all/send_redirects
|
||||
echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
|
||||
# 本地的话,貌似这段不需要
|
||||
# iptables -t mangle -N DIVERT >/dev/null 2>&1
|
||||
# iptables -t mangle -F DIVERT
|
||||
# iptables -t mangle -D PREROUTING -p tcp -m socket -j DIVERT >/dev/null 2>&1
|
||||
# iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
|
||||
# iptables -t mangle -A DIVERT -j MARK --set-mark 1
|
||||
# iptables -t mangle -A DIVERT -j ACCEPT
|
||||
|
||||
ip rule del fwmark 1 lookup 100
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
ip rule del from ${SOURCE_BIND_IP} table 101
|
||||
ip rule add from ${SOURCE_BIND_IP} table 101
|
||||
ip route del default via 127.0.0.1 dev lo table 101
|
||||
ip route add default via 127.0.0.1 dev lo table 101
|
||||
|
||||
ip route flush cache
|
||||
ip ro flush cache
|
||||
19
docker/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM golang:alpine AS builder
|
||||
WORKDIR $GOPATH
|
||||
ARG GOPROXY_VERSION=master
|
||||
RUN apk update; apk upgrade; \
|
||||
apk add --no-cache ca-certificates git; \
|
||||
cd /go/src/; \
|
||||
mkdir -p github.com/snail007; \
|
||||
cd github.com/snail007; \
|
||||
git clone --depth=1 https://github.com/snail007/goproxy.git; \
|
||||
cd goproxy; \
|
||||
git checkout ${GOPROXY_VERSION}; \
|
||||
CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -a -installsuffix cgo -o proxy; \
|
||||
chmod 0777 proxy
|
||||
|
||||
FROM debian:stable-slim
|
||||
COPY --from=builder /go/src/github.com/snail007/goproxy/proxy /usr/local/bin/
|
||||
# RUN chmod 0777 /usr/local/bin/proxy
|
||||
EXPOSE 80 443
|
||||
CMD /usr/local/bin/proxy http -t tcp -p :80,:443
|
||||
BIN
docs/images/1.1.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/images/2.1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/2.2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/5.2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/alipay.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/images/fxdl.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/images/http-1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/http-2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/http-kcp.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/http-ssh-1.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/images/http-tls-2.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/http-tls-3.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/images/socks-2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/socks-ssh.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/images/socks-tls-2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/images/socks-tls-3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/sps-tls.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/images/tcp-1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/tcp-2.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/images/tcp-3.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/tcp-tls-2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/images/tcp-tls-3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/udp-1.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/udp-2.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/images/udp-3.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/images/udp-tls-2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/udp-tls-3.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/wxpay.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
25
docs/old-release-zh.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Old Versions of Proxy
|
||||
|
||||
- [v5.3手册](https://github.com/snail007/goproxy/tree/v5.3)
|
||||
- [v5.2手册](https://github.com/snail007/goproxy/tree/v5.2)
|
||||
- [v5.1手册](https://github.com/snail007/goproxy/tree/v5.1)
|
||||
- [v5.0手册](https://github.com/snail007/goproxy/tree/v5.0)
|
||||
- [v4.9手册](https://github.com/snail007/goproxy/tree/v4.9)
|
||||
- [v4.8手册](https://github.com/snail007/goproxy/tree/v4.8)
|
||||
- [v4.7手册](https://github.com/snail007/goproxy/tree/v4.7)
|
||||
- [v4.6手册](https://github.com/snail007/goproxy/tree/v4.6)
|
||||
- [v4.5手册](https://github.com/snail007/goproxy/tree/v4.5)
|
||||
- [v4.4手册](https://github.com/snail007/goproxy/tree/v4.4)
|
||||
- [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3)
|
||||
- [v4.2手册](https://github.com/snail007/goproxy/tree/v4.2)
|
||||
- [v4.0-v4.1手册](https://github.com/snail007/goproxy/tree/v4.1)
|
||||
- [v3.9手册](https://github.com/snail007/goproxy/tree/v3.9)
|
||||
- [v3.8手册](https://github.com/snail007/goproxy/tree/v3.8)
|
||||
- [v3.6-v3.7手册](https://github.com/snail007/goproxy/tree/v3.6)
|
||||
- [v3.5手册](https://github.com/snail007/goproxy/tree/v3.5)
|
||||
- [v3.4手册](https://github.com/snail007/goproxy/tree/v3.4)
|
||||
- [v3.3手册](https://github.com/snail007/goproxy/tree/v3.3)
|
||||
- [v3.2手册](https://github.com/snail007/goproxy/tree/v3.2)
|
||||
- [v3.1手册](https://github.com/snail007/goproxy/tree/v3.1)
|
||||
- [v3.0手册](https://github.com/snail007/goproxy/tree/v3.0)
|
||||
- [v2.x手册](https://github.com/snail007/goproxy/tree/v2.2)
|
||||
25
docs/old-release.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Old Versions of Proxy
|
||||
|
||||
- [v5.3 Manual](https://github.com/snail007/goproxy/tree/v5.3)
|
||||
- [v5.2 Manual](https://github.com/snail007/goproxy/tree/v5.2)
|
||||
- [v5.1 Manual](https://github.com/snail007/goproxy/tree/v5.1)
|
||||
- [v5.0 Manual](https://github.com/snail007/goproxy/tree/v5.0)
|
||||
- [v4.9 Manual](https://github.com/snail007/goproxy/tree/v4.9)
|
||||
- [v4.8 Manual](https://github.com/snail007/goproxy/tree/v4.8)
|
||||
- [v4.7 Manual](https://github.com/snail007/goproxy/tree/v4.7)
|
||||
- [v4.6 Manual](https://github.com/snail007/goproxy/tree/v4.6)
|
||||
- [v4.5 Manual](https://github.com/snail007/goproxy/tree/v4.5)
|
||||
- [v4.4 Manual](https://github.com/snail007/goproxy/tree/v4.4)
|
||||
- [v4.3 Manual](https://github.com/snail007/goproxy/tree/v4.3)
|
||||
- [v4.2 Manual](https://github.com/snail007/goproxy/tree/v4.2)
|
||||
- [v4.0-v4.1 Manual](https://github.com/snail007/goproxy/tree/v4.1)
|
||||
- [v3.9 Manual](https://github.com/snail007/goproxy/tree/v3.9)
|
||||
- [v3.8 Manual](https://github.com/snail007/goproxy/tree/v3.8)
|
||||
- [v3.6-v3.7 Manual](https://github.com/snail007/goproxy/tree/v3.6)
|
||||
- [v3.5 Manual](https://github.com/snail007/goproxy/tree/v3.5)
|
||||
- [v3.4 Manual](https://github.com/snail007/goproxy/tree/v3.4)
|
||||
- [v3.3 Manual](https://github.com/snail007/goproxy/tree/v3.3)
|
||||
- [v3.2 Manual](https://github.com/snail007/goproxy/tree/v3.2)
|
||||
- [v3.1 Manual](https://github.com/snail007/goproxy/tree/v3.1)
|
||||
- [v3.0 Manual](https://github.com/snail007/goproxy/tree/v3.0)
|
||||
- [v2.x Manual](https://github.com/snail007/goproxy/tree/v2.2)
|
||||
27
gui/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Proxy-GUI
|
||||
Based on the proxy platform SDK, the author and many enthusiasts have developed the GUI version of the proxy for each platform.
|
||||
|
||||
## Windows
|
||||
|
||||
- Official java version, Project Homepage:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||
|
||||
## Linux
|
||||
|
||||
- Official java version, Project Homepage:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||
|
||||
## MacOS
|
||||
|
||||
- Coming Soon ...
|
||||
|
||||
## Android
|
||||
|
||||
- proxy-go,An unofficial implementation version, the interface is relatively simple, but enough.Download address:[proxy-go](https://github.com/snail007/goproxy-gui-stuff/releases/tag/proxy-go-release)
|
||||
|
||||
|
||||
## IOS
|
||||
|
||||
- Coming Soon ...
|
||||
|
||||
## Cross-platform
|
||||
|
||||
- proxy-web,A cross-platform web UI version,project home page:[proxy-web](https://github.com/yincongcyincong/proxy-web)
|
||||
27
gui/README_ZH.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Proxy-GUI
|
||||
基于proxy的各平台SDK,作者和众多热心人士开发了各平台的GUI版本的proxy,下面分平台介绍.
|
||||
|
||||
## Windows
|
||||
|
||||
- 官方java版本,项目主页:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||
|
||||
## Linux
|
||||
|
||||
- 官方java版本,项目主页:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||
|
||||
## MacOS
|
||||
|
||||
- Coming Soon ...
|
||||
|
||||
## Android
|
||||
|
||||
- proxy-go,一个非官方实现版本,界面比较简陋,但是够用.下载地址:[proxy-go](https://github.com/snail007/goproxy-gui-stuff/releases/tag/proxy-go-release)
|
||||
|
||||
|
||||
## IOS
|
||||
|
||||
- Coming Soon ...
|
||||
|
||||
## 跨平台
|
||||
|
||||
- proxy-web,一个跨平台的web UI版本,项目主页:[proxy-web](https://github.com/yincongcyincong/proxy-web)
|
||||
@ -1,12 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# install monexec
|
||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
||||
cd monexec_0.1.1_linux_amd64
|
||||
cp monexec /usr/bin/
|
||||
chmod +x /usr/bin/monexec
|
||||
cd ..
|
||||
# #install proxy
|
||||
tar zxvf proxy-linux-amd64.tar.gz
|
||||
cp proxy /usr/bin/
|
||||
|
||||
@ -5,15 +5,11 @@ if [ -e /tmp/proxy ]; then
|
||||
fi
|
||||
mkdir /tmp/proxy
|
||||
cd /tmp/proxy
|
||||
wget https://github.com/reddec/monexec/releases/download/v0.1.1/monexec_0.1.1_linux_amd64.tar.gz
|
||||
wget https://github.com/snail007/goproxy/releases/download/v3.8/proxy-linux-amd64.tar.gz
|
||||
|
||||
# install monexec
|
||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
||||
cd monexec_0.1.1_linux_amd64
|
||||
cp monexec /usr/bin/
|
||||
chmod +x /usr/bin/monexec
|
||||
cd ..
|
||||
LAST_VERSION=$(curl --silent "https://api.github.com/repos/snail007/goproxy/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')
|
||||
|
||||
wget "https://github.com/snail007/goproxy/releases/download/${LAST_VERSION}/proxy-linux-amd64.tar.gz"
|
||||
|
||||
# #install proxy
|
||||
tar zxvf proxy-linux-amd64.tar.gz
|
||||
cp proxy /usr/bin/
|
||||
@ -26,7 +22,7 @@ fi
|
||||
|
||||
if [ ! -e /etc/proxy/proxy.crt ]; then
|
||||
cd /etc/proxy/
|
||||
proxy keygen >/dev/null 2>&1
|
||||
proxy keygen -C proxy >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/proxy
|
||||
echo "install done"
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
openssl genrsa -out proxy.key 2048
|
||||
openssl req -new -key proxy.key -x509 -days 3650 -out proxy.crt -subj /C=CN/ST=BJ/O="Localhost Ltd"/CN=proxy
|
||||
33
main.go
@ -5,18 +5,24 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"proxy/services"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/snail007/goproxy/services"
|
||||
)
|
||||
|
||||
const APP_VERSION = "3.8"
|
||||
var APP_VERSION = "No Version Provided"
|
||||
|
||||
func main() {
|
||||
err := initConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("err : %s", err)
|
||||
}
|
||||
Clean(&service.S)
|
||||
if service != nil && service.S != nil {
|
||||
Clean(&service.S)
|
||||
} else {
|
||||
Clean(nil)
|
||||
}
|
||||
}
|
||||
func Clean(s *services.Service) {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
@ -28,11 +34,26 @@ func Clean(s *services.Service) {
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT)
|
||||
go func() {
|
||||
for _ = range signalChan {
|
||||
fmt.Println("\nReceived an interrupt, stopping services...")
|
||||
(*s).Clean()
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:\n%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for range signalChan {
|
||||
log.Println("Received an interrupt, stopping services...")
|
||||
if s != nil && *s != nil {
|
||||
(*s).Clean()
|
||||
}
|
||||
if cmd != nil {
|
||||
log.Printf("clean process %d", cmd.Process.Pid)
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
if *isDebug {
|
||||
saveProfiling()
|
||||
}
|
||||
cleanupDone <- true
|
||||
}
|
||||
}()
|
||||
<-cleanupDone
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
113
release.sh
@ -1,69 +1,84 @@
|
||||
#!/bin/bash
|
||||
VER="3.8"
|
||||
RELEASE="release-${VER}"
|
||||
VERSION=$(cat VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X main.APP_VERSION=$VER"
|
||||
RELEASE="release-${VERSION}"
|
||||
TRIMPATH1="/Users/snail/go/src/github.com/snail007"
|
||||
TRIMPATH=$(dirname ~/go/src/github.com/snail007)/snail007
|
||||
if [ -d "$TRIMPATH1" ];then
|
||||
TRIMPATH=$TRIMPATH1
|
||||
fi
|
||||
OPTS="-gcflags=-trimpath=$TRIMPATH -asmflags=-trimpath=$TRIMPATH"
|
||||
|
||||
rm -rf .cert
|
||||
mkdir .cert
|
||||
go build
|
||||
go build $OPTS -ldflags "$X" -o proxy
|
||||
cd .cert
|
||||
../proxy keygen
|
||||
../proxy keygen -C proxy
|
||||
cd ..
|
||||
rm -rf ${RELEASE}
|
||||
mkdir ${RELEASE}
|
||||
export CGO_ENABLED=0
|
||||
#linux
|
||||
GOOS=linux GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=arm GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=arm64 GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm64.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=mips go build && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=mips64 go build && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=mips64le go build && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=mipsle go build && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=ppc64 go build && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=ppc64le go build && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy direct blocked
|
||||
GOOS=linux GOARCH=s390x go build && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v6.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=6 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v6.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v7.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v7.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v5.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=5 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v5.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v8.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v8.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips-softfloat.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 GOMIPS=softfloat go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64-softfloat.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le GOMIPS=softfloat go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64le-softfloat.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-mipsle-softfloat.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy direct blocked
|
||||
#android
|
||||
GOOS=android GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy direct blocked
|
||||
GOOS=android GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=android GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy direct blocked
|
||||
GOOS=android GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=android GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=android GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=android GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy direct blocked
|
||||
#darwin
|
||||
GOOS=darwin GOARCH=386 go build go build && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy direct blocked
|
||||
GOOS=darwin GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=darwin GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy direct blocked
|
||||
GOOS=darwin GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy direct blocked
|
||||
#dragonfly
|
||||
GOOS=dragonfly GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy direct blocked
|
||||
#freebsd
|
||||
GOOS=freebsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy direct blocked
|
||||
GOOS=freebsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=freebsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy direct blocked
|
||||
#nacl
|
||||
GOOS=nacl GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy direct blocked
|
||||
GOOS=nacl GOARCH=amd64p32 go build && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy direct blocked
|
||||
GOOS=nacl GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=nacl GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=nacl GOARCH=amd64p32 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=nacl GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy direct blocked
|
||||
#netbsd
|
||||
GOOS=netbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy direct blocked
|
||||
GOOS=netbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=netbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy direct blocked
|
||||
#openbsd
|
||||
GOOS=openbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy direct blocked
|
||||
GOOS=openbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=openbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy direct blocked
|
||||
#plan9
|
||||
GOOS=plan9 GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy direct blocked
|
||||
GOOS=plan9 GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy direct blocked
|
||||
GOOS=plan9 GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=plan9 GOARCH=386 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=plan9 GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=plan9 GOARCH=arm go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy direct blocked
|
||||
#solaris
|
||||
GOOS=solaris GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy direct blocked
|
||||
CGO_ENABLED=0 GOOS=solaris GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy direct blocked
|
||||
#windows
|
||||
GOOS=windows GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||
GOOS=windows GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build $OPTS -ldflags="-H=windowsgui $X" -o proxy-noconsole.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build $OPTS -ldflags "$X" -o proxy.exe && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe proxy-noconsole.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $OPTS -ldflags="-H=windowsgui $X" -o proxy-noconsole.exe
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $OPTS -ldflags "$X" -o proxy.exe && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe proxy-noconsole.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||
|
||||
rm -rf proxy proxy.exe .cert
|
||||
rm -rf proxy proxy.exe proxy-noconsole.exe .cert
|
||||
|
||||
#todo
|
||||
#1.release.sh VER="xxx"
|
||||
#2.main.go APP_VERSION="xxx"
|
||||
#3.install_auto.sh goproxy/releases/download/vxxx
|
||||
#4.README goproxy/releases/download/vxxx
|
||||
7
sdk/android-ios/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
*.jar
|
||||
*.aar
|
||||
*.tar.gz
|
||||
ios
|
||||
android
|
||||
Proxy.framework
|
||||
|
||||
277
sdk/android-ios/dns.go
Normal file
@ -0,0 +1,277 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
logger "log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
gocache "github.com/pmylund/go-cache"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
services "github.com/snail007/goproxy/services"
|
||||
)
|
||||
|
||||
type DNSArgs struct {
|
||||
ParentServiceType *string
|
||||
ParentType *string
|
||||
Parent *string
|
||||
ParentAuth *string
|
||||
ParentKey *string
|
||||
ParentCompress *bool
|
||||
KCP kcpcfg.KCPConfigArgs
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CaCertFile *string
|
||||
Local *string
|
||||
Timeout *int
|
||||
RemoteDNSAddress *string
|
||||
DNSTTL *int
|
||||
CacheFile *string
|
||||
LocalSocks5Port *string
|
||||
}
|
||||
type DNS struct {
|
||||
cfg DNSArgs
|
||||
log *logger.Logger
|
||||
cache *gocache.Cache
|
||||
exitSig chan bool
|
||||
serviceKey string
|
||||
dialer proxy.Dialer
|
||||
}
|
||||
|
||||
func NewDNS() services.Service {
|
||||
return &DNS{
|
||||
cfg: DNSArgs{},
|
||||
exitSig: make(chan bool, 1),
|
||||
serviceKey: "dns-service-" + fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
}
|
||||
}
|
||||
func (s *DNS) CheckArgs() (err error) {
|
||||
return
|
||||
}
|
||||
func (s *DNS) InitService() (err error) {
|
||||
s.cache = gocache.New(time.Second*time.Duration(*s.cfg.DNSTTL), time.Second*60)
|
||||
s.cache.LoadFile(*s.cfg.CacheFile)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-s.exitSig:
|
||||
return
|
||||
case <-time.After(time.Second * 300):
|
||||
s.cache.DeleteExpired()
|
||||
s.cache.SaveFile(*s.cfg.CacheFile)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-s.exitSig:
|
||||
return
|
||||
case <-time.After(time.Second * 60):
|
||||
err := s.cache.SaveFile(*s.cfg.CacheFile)
|
||||
if err == nil {
|
||||
//s.log.Printf("cache saved: %s", *s.cfg.CacheFile)
|
||||
} else {
|
||||
s.log.Printf("cache save failed: %s, %s", *s.cfg.CacheFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
s.dialer, err = proxy.SOCKS5("tcp", *s.cfg.Parent,
|
||||
nil,
|
||||
&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 2 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sdkArgs := fmt.Sprintf("sps -S %s -T %s -P %s -C %s -K %s -i %d -p 127.0.0.1:%s --disable-http",
|
||||
*s.cfg.ParentServiceType,
|
||||
*s.cfg.ParentType,
|
||||
*s.cfg.Parent,
|
||||
*s.cfg.CertFile,
|
||||
*s.cfg.KeyFile,
|
||||
*s.cfg.Timeout,
|
||||
*s.cfg.LocalSocks5Port,
|
||||
)
|
||||
if *s.cfg.ParentKey != "" {
|
||||
sdkArgs += " -Z " + *s.cfg.ParentKey
|
||||
}
|
||||
if *s.cfg.ParentAuth != "" {
|
||||
sdkArgs += " -A " + *s.cfg.ParentAuth
|
||||
}
|
||||
if *s.cfg.CaCertFile != "" {
|
||||
sdkArgs += " --ca " + *s.cfg.CaCertFile
|
||||
}
|
||||
if *s.cfg.ParentCompress {
|
||||
sdkArgs += " -M"
|
||||
}
|
||||
s.log.Printf("start sps with : %s", sdkArgs)
|
||||
errStr := Start(s.serviceKey, sdkArgs)
|
||||
if errStr != "" {
|
||||
err = fmt.Errorf("start sps service fail,%s", errStr)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *DNS) StopService() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stop dns service crashed,%s", e)
|
||||
} else {
|
||||
s.log.Printf("service dns stopped")
|
||||
}
|
||||
}()
|
||||
Stop(s.serviceKey)
|
||||
s.cache.Flush()
|
||||
s.exitSig <- true
|
||||
}
|
||||
func (s *DNS) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(DNSArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
dns.HandleFunc(".", s.callback)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
log.Printf("dns server on udp %s", *s.cfg.Local)
|
||||
err := dns.ListenAndServe(*s.cfg.Local, "udp", nil)
|
||||
if err != nil {
|
||||
log.Printf("dns listen error: %s", err)
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DNS) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *DNS) callback(w dns.ResponseWriter, req *dns.Msg) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
s.log.Printf("dns handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
var (
|
||||
key string
|
||||
m *dns.Msg
|
||||
err error
|
||||
data []byte
|
||||
id uint16
|
||||
query []string
|
||||
questions []dns.Question
|
||||
)
|
||||
if req.MsgHdr.Response == true {
|
||||
return
|
||||
}
|
||||
query = make([]string, len(req.Question))
|
||||
for i, q := range req.Question {
|
||||
if q.Qtype != dns.TypeAAAA {
|
||||
questions = append(questions, q)
|
||||
}
|
||||
query[i] = fmt.Sprintf("(%s %s %s)", q.Name, dns.ClassToString[q.Qclass], dns.TypeToString[q.Qtype])
|
||||
}
|
||||
|
||||
if len(questions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
req.Question = questions
|
||||
id = req.Id
|
||||
req.Id = 0
|
||||
key = s.toMd5(req.String())
|
||||
req.Id = id
|
||||
if reply, ok := s.cache.Get(key); ok {
|
||||
data, _ = reply.([]byte)
|
||||
}
|
||||
if data != nil && len(data) > 0 {
|
||||
m = &dns.Msg{}
|
||||
m.Unpack(data)
|
||||
m.Id = id
|
||||
err = w.WriteMsg(m)
|
||||
s.log.Printf("id: %5d cache: HIT %v", id, query)
|
||||
return
|
||||
|
||||
} else {
|
||||
s.log.Printf("id: %5d cache: MISS %v", id, query)
|
||||
}
|
||||
|
||||
s.log.Printf("id: %5d resolve: %v %s", id, query, *s.cfg.RemoteDNSAddress)
|
||||
|
||||
rawConn, err := s.dialer.Dial("tcp", *s.cfg.RemoteDNSAddress)
|
||||
if err != nil {
|
||||
s.log.Printf("dail to %s fail,%s", *s.cfg.RemoteDNSAddress, err)
|
||||
return
|
||||
}
|
||||
defer rawConn.Close()
|
||||
co := new(dns.Conn)
|
||||
co.Conn = rawConn
|
||||
defer co.Close()
|
||||
if err = co.WriteMsg(req); err != nil {
|
||||
s.log.Printf("write dns query fail,%s", err)
|
||||
return
|
||||
}
|
||||
m, err = co.ReadMsg()
|
||||
if err == nil && m.Id != req.Id {
|
||||
s.log.Printf("id: %5d mismath", id)
|
||||
return
|
||||
}
|
||||
if err != nil || len(m.Answer) == 0 {
|
||||
s.log.Printf("dns query fail,%s", err)
|
||||
return
|
||||
}
|
||||
data, err = m.Pack()
|
||||
if err != nil {
|
||||
s.log.Printf("dns query fail,%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
s.log.Printf("dns query fail,%s", err)
|
||||
return
|
||||
}
|
||||
m.Id = 0
|
||||
data, _ = m.Pack()
|
||||
ttl := 0
|
||||
if len(m.Answer) > 0 {
|
||||
if *s.cfg.DNSTTL > 0 {
|
||||
ttl = *s.cfg.DNSTTL
|
||||
} else {
|
||||
ttl = int(m.Answer[0].Header().Ttl)
|
||||
if ttl < 0 {
|
||||
ttl = *s.cfg.DNSTTL
|
||||
}
|
||||
}
|
||||
}
|
||||
s.cache.Set(key, data, time.Second*time.Duration(ttl))
|
||||
m.Id = id
|
||||
s.log.Printf("id: %5d cache: CACHED %v TTL %v", id, query, ttl)
|
||||
}
|
||||
func (s *DNS) toMd5(data string) string {
|
||||
m := md5.New()
|
||||
m.Write([]byte(data))
|
||||
return hex.EncodeToString(m.Sum(nil))
|
||||
}
|
||||
27
sdk/android-ios/release_android.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#/bin/bash
|
||||
VERSION=$(cat ../../VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X github.com/snail007/goproxy/sdk/android-ios.SDK_VERSION=$VER -X main.APP_VERSION=$VER"
|
||||
|
||||
rm -rf sdk-android-*.tar.gz
|
||||
rm -rf android
|
||||
mkdir android
|
||||
|
||||
#android ; jdk,android ndk & android sdk required, install gomobile go1.10 required
|
||||
#export GOPATH="$HOME/go"
|
||||
#export GOROOT="/usr/local/go"
|
||||
#export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
|
||||
#export ANDROID_HOME="$HOME/Android/Sdk"
|
||||
#export NDK_ROOT="$HOME/Android/Sdk/ndk-bundle"
|
||||
#export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$NDK_ROOT:$PATH"
|
||||
#go get -v golang.org/x/mobile/cmd/gomobile
|
||||
#gomobile init
|
||||
|
||||
gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w $X"
|
||||
mv proxy.aar android/snail007.goproxy.sdk.aar
|
||||
mv proxy-sources.jar android/snail007.goproxy.sdk-sources.jar
|
||||
cp ../README.md android
|
||||
tar zcfv sdk-android-${VERSION}.tar.gz android
|
||||
rm -rf android
|
||||
|
||||
echo "done."
|
||||
17
sdk/android-ios/release_ios.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#/bin/bash
|
||||
VERSION=$(cat ../../VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X github.com/snail007/goproxy/sdk/android-ios.SDK_VERSION=$VER -X main.APP_VERSION=$VER"
|
||||
|
||||
rm -rf sdk-ios-*.tar.gz
|
||||
rm -rf ios
|
||||
mkdir ios
|
||||
|
||||
#ios XCode required
|
||||
gomobile bind -v -target=ios -ldflags="-s -w $X"
|
||||
mv Proxy.framework ios
|
||||
cp ../README.md ios
|
||||
tar zcfv sdk-ios-${VERSION}.tar.gz ios
|
||||
rm -rf ios
|
||||
|
||||
echo "done."
|
||||
524
sdk/android-ios/sdk.go
Normal file
@ -0,0 +1,524 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
logger "log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
"github.com/snail007/goproxy/services"
|
||||
httpx "github.com/snail007/goproxy/services/http"
|
||||
keygenx "github.com/snail007/goproxy/services/keygen"
|
||||
mux "github.com/snail007/goproxy/services/mux"
|
||||
socksx "github.com/snail007/goproxy/services/socks"
|
||||
spsx "github.com/snail007/goproxy/services/sps"
|
||||
tcpx "github.com/snail007/goproxy/services/tcp"
|
||||
tunnelx "github.com/snail007/goproxy/services/tunnel"
|
||||
udpx "github.com/snail007/goproxy/services/udp"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var SDK_VERSION = "No Version Provided"
|
||||
|
||||
var (
|
||||
app *kingpin.Application
|
||||
cpuProfilingFile, memProfilingFile, blockProfilingFile,
|
||||
goroutineProfilingFile, threadcreateProfilingFile *os.File
|
||||
isProfiling bool
|
||||
profilingLock = &sync.Mutex{}
|
||||
)
|
||||
|
||||
type LogCallback interface {
|
||||
Write(line string)
|
||||
}
|
||||
type logCallback interface {
|
||||
Write(line string)
|
||||
}
|
||||
type logWriter struct {
|
||||
callback LogCallback
|
||||
}
|
||||
|
||||
func (s *logWriter) Write(p []byte) (n int, err error) {
|
||||
s.callback.Write(string(p))
|
||||
return
|
||||
}
|
||||
|
||||
func Start(serviceID, serviceArgsStr string) (errStr string) {
|
||||
return StartWithLog(serviceID, serviceArgsStr, nil)
|
||||
}
|
||||
|
||||
//Start
|
||||
//serviceID : is service identify id,different service's id should be difference
|
||||
//serviceArgsStr: is the whole command line args string
|
||||
//such as :
|
||||
//1."http -t tcp -p :8989"
|
||||
//2."socks -t tcp -p :8989"
|
||||
//and so on.
|
||||
//if an error occured , errStr will be the error reason
|
||||
//if start success, errStr is empty.
|
||||
func StartWithLog(serviceID, serviceArgsStr string, loggerCallback LogCallback) (errStr string) {
|
||||
//define args
|
||||
tcpArgs := tcpx.TCPArgs{}
|
||||
httpArgs := httpx.HTTPArgs{}
|
||||
tunnelServerArgs := tunnelx.TunnelServerArgs{}
|
||||
tunnelClientArgs := tunnelx.TunnelClientArgs{}
|
||||
tunnelBridgeArgs := tunnelx.TunnelBridgeArgs{}
|
||||
muxServerArgs := mux.MuxServerArgs{}
|
||||
muxClientArgs := mux.MuxClientArgs{}
|
||||
muxBridgeArgs := mux.MuxBridgeArgs{}
|
||||
udpArgs := udpx.UDPArgs{}
|
||||
socksArgs := socksx.SocksArgs{}
|
||||
spsArgs := spsx.SPSArgs{}
|
||||
dnsArgs := DNSArgs{}
|
||||
keygenArgs := keygenx.KeygenArgs{}
|
||||
kcpArgs := kcpcfg.KCPConfigArgs{}
|
||||
//build srvice args
|
||||
app = kingpin.New("proxy", "happy with proxy")
|
||||
app.Author("snail").Version(SDK_VERSION)
|
||||
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
||||
logfile := app.Flag("log", "log file path").Default("").String()
|
||||
nolog := app.Flag("nolog", "turn off logging").Default("false").Bool()
|
||||
kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String()
|
||||
kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none")
|
||||
kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast3").Enum("fast3", "fast2", "fast", "normal", "manual")
|
||||
kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int()
|
||||
kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int()
|
||||
kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int()
|
||||
kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int()
|
||||
kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int()
|
||||
kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int()
|
||||
kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool()
|
||||
kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool()
|
||||
kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int()
|
||||
kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int()
|
||||
kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int()
|
||||
kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int()
|
||||
kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int()
|
||||
kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int()
|
||||
|
||||
//########http#########
|
||||
http := app.Command("http", "proxy on http mode")
|
||||
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').Strings()
|
||||
httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
httpArgs.LocalType = http.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
httpArgs.ParentType = http.Flag("parent-type", "parent protocol type <tls|tcp|ssh|kcp>").Short('T').Enum("tls", "tcp", "ssh", "kcp")
|
||||
httpArgs.Always = http.Flag("always", "always use parent proxy").Default("false").Bool()
|
||||
httpArgs.Timeout = http.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
|
||||
httpArgs.HTTPTimeout = http.Flag("http-timeout", "check domain if blocked , http request timeout milliseconds when connect to host").Default("3000").Int()
|
||||
httpArgs.Interval = http.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int()
|
||||
httpArgs.Blocked = http.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String()
|
||||
httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||
httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||
httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||
httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||
httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||
httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||
httpArgs.LocalIPS = http.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int()
|
||||
httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
httpArgs.Intelligent = http.Flag("intelligent", "settting intelligent HTTP, SOCKS5 proxy mode, can be <intelligent|direct|parent>").Default("intelligent").Enum("intelligent", "direct", "parent")
|
||||
httpArgs.LoadBalanceMethod = http.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("roundrobin").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
httpArgs.LoadBalanceTimeout = http.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
httpArgs.LoadBalanceRetryTime = http.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
httpArgs.LoadBalanceHashTarget = http.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
httpArgs.LoadBalanceOnlyHA = http.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
httpArgs.RateLimit = http.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
httpArgs.BindListen = http.Flag("bind-listen", "using listener binding IP when connect to target").Short('B').Default("false").Bool()
|
||||
httpArgs.Jumper = http.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
httpArgs.Debug = debug
|
||||
//########tcp#########
|
||||
tcp := app.Command("tcp", "proxy on tcp mode")
|
||||
tcpArgs.Parent = tcp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
tcpArgs.CertFile = tcp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
tcpArgs.KeyFile = tcp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int()
|
||||
tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type <tls|tcp|kcp|udp>").Short('T').Enum("tls", "tcp", "udp", "kcp")
|
||||
tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
tcpArgs.Jumper = tcp.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########udp#########
|
||||
udp := app.Command("udp", "proxy on udp mode")
|
||||
udpArgs.Parent = udp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
udpArgs.CertFile = udp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int()
|
||||
udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type <tls|tcp|udp>").Short('T').Enum("tls", "tcp", "udp")
|
||||
udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||
udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
|
||||
//########mux-server#########
|
||||
muxServer := app.Command("server", "proxy on mux server mode")
|
||||
muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('T').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool()
|
||||
muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String()
|
||||
muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||
muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||
muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||
muxServerArgs.Jumper = muxServer.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
muxServerArgs.TCPSMethod = muxServer.Flag("tcps-method", "method of parent tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxServerArgs.TCPSPassword = muxServer.Flag("tcps-password", "password of parent tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxServerArgs.TOUMethod = muxServer.Flag("tou-method", "method of parent tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxServerArgs.TOUPassword = muxServer.Flag("tou-password", "password of parent tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########mux-client#########
|
||||
muxClient := app.Command("client", "proxy on mux client mode")
|
||||
muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('T').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String()
|
||||
muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||
muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||
muxClientArgs.Jumper = muxClient.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
muxClientArgs.TCPSMethod = muxClient.Flag("tcps-method", "method of parent tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxClientArgs.TCPSPassword = muxClient.Flag("tcps-password", "password of parent tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxClientArgs.TOUMethod = muxClient.Flag("tou-method", "method of parent tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxClientArgs.TOUPassword = muxClient.Flag("tou-password", "password of parent tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########mux-bridge#########
|
||||
muxBridge := app.Command("bridge", "proxy on mux bridge mode")
|
||||
muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||
muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type <tls|tcp|tcps|kcp|tou>").Default("tls").Short('t').Enum("tls", "tcp", "tcps", "kcp", "tou")
|
||||
muxBridgeArgs.TCPSMethod = muxBridge.Flag("tcps-method", "method of local tcps's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxBridgeArgs.TCPSPassword = muxBridge.Flag("tcps-password", "password of local tcps's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
muxBridgeArgs.TOUMethod = muxBridge.Flag("tou-method", "method of local tou's encrpyt/decrypt, these below are supported :\n"+strings.Join(encryptconn.GetCipherMethods(), ",")).Default("aes-192-cfb").String()
|
||||
muxBridgeArgs.TOUPassword = muxBridge.Flag("tou-password", "password of local tou's encrpyt/decrypt").Default("snail007's_goproxy").String()
|
||||
|
||||
//########tunnel-server#########
|
||||
tunnelServer := app.Command("tserver", "proxy on tunnel server mode")
|
||||
tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
tunnelServerArgs.CertFile = tunnelServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
tunnelServerArgs.KeyFile = tunnelServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool()
|
||||
tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String()
|
||||
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||
tunnelServerArgs.Jumper = tunnelServer.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########tunnel-client#########
|
||||
tunnelClient := app.Command("tclient", "proxy on tunnel client mode")
|
||||
tunnelClientArgs.Parent = tunnelClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
tunnelClientArgs.CertFile = tunnelClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
tunnelClientArgs.KeyFile = tunnelClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tunnelClientArgs.Timeout = tunnelClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelClientArgs.Key = tunnelClient.Flag("k", "key same with server").Default("default").String()
|
||||
tunnelClientArgs.Jumper = tunnelClient.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Short('J').Default("").String()
|
||||
|
||||
//########tunnel-bridge#########
|
||||
tunnelBridge := app.Command("tbridge", "proxy on tunnel bridge mode")
|
||||
tunnelBridgeArgs.CertFile = tunnelBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
tunnelBridgeArgs.KeyFile = tunnelBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
tunnelBridgeArgs.Timeout = tunnelBridge.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||
tunnelBridgeArgs.Local = tunnelBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
|
||||
//########ssh#########
|
||||
socks := app.Command("socks", "proxy on ssh mode")
|
||||
socksArgs.Parent = socks.Flag("parent", "parent ssh address, such as: \"23.32.32.19:22\"").Default("").Short('P').Strings()
|
||||
socksArgs.ParentType = socks.Flag("parent-type", "parent protocol type <tls|tcp|kcp|ssh>").Default("tcp").Short('T').Enum("tls", "tcp", "kcp", "ssh")
|
||||
socksArgs.LocalType = socks.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
socksArgs.Local = socks.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||
socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
socksArgs.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||
socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||
socksArgs.SSHKeyFileSalt = socks.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||
socksArgs.SSHPassword = socks.Flag("ssh-password", "password for ssh").Short('D').Default("").String()
|
||||
socksArgs.Always = socks.Flag("always", "always use parent proxy").Default("false").Bool()
|
||||
socksArgs.Timeout = socks.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("5000").Int()
|
||||
socksArgs.Interval = socks.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int()
|
||||
socksArgs.Blocked = socks.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String()
|
||||
socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||
socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
socksArgs.LocalIPS = socks.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||
socksArgs.ParentAuth = socks.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
socksArgs.Intelligent = socks.Flag("intelligent", "settting intelligent HTTP, SOCKS5 proxy mode, can be <intelligent|direct|parent>").Default("intelligent").Enum("intelligent", "direct", "parent")
|
||||
socksArgs.LoadBalanceMethod = socks.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("roundrobin").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
socksArgs.LoadBalanceTimeout = socks.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
socksArgs.LoadBalanceRetryTime = socks.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
socksArgs.LoadBalanceHashTarget = socks.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
socksArgs.LoadBalanceOnlyHA = socks.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
socksArgs.RateLimit = socks.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
socksArgs.BindListen = socks.Flag("bind-listen", "using listener binding IP when connect to target").Short('B').Default("false").Bool()
|
||||
socksArgs.Debug = debug
|
||||
|
||||
//########socks+http(s)#########
|
||||
sps := app.Command("sps", "proxy on socks+http(s) mode")
|
||||
spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').Strings()
|
||||
spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||
spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||
spsArgs.LocalType = sps.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||
spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||
spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type <http|socks|ss>").Short('S').Enum("http", "socks", "ss")
|
||||
spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||
spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||
spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||
spsArgs.LocalIPS = sps.Flag("local-bind-ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||
spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||
spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||
spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||
spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||
spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||
spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||
spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
spsArgs.SSMethod = sps.Flag("ss-method", "the following methods are supported: aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, rc4-md5-6, chacha20, salsa20, rc4, table, des-cfb, chacha20-ietf; if you use ss client , \"-t tcp\" is required").Short('h').Default("aes-256-cfb").String()
|
||||
spsArgs.SSKey = sps.Flag("ss-key", "if you use ss client , \"-t tcp\" is required").Short('j').Default("sspassword").String()
|
||||
spsArgs.ParentSSMethod = sps.Flag("parent-ss-method", "the following methods are supported: aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, rc4-md5-6, chacha20, salsa20, rc4, table, des-cfb, chacha20-ietf; if you use ss server as parent, \"-T tcp\" is required").Short('H').Default("aes-256-cfb").String()
|
||||
spsArgs.ParentSSKey = sps.Flag("parent-ss-key", "if you use ss server as parent, \"-T tcp\" is required").Short('J').Default("sspassword").String()
|
||||
spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool()
|
||||
spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
|
||||
spsArgs.DisableSS = sps.Flag("disable-ss", "disable ss proxy").Default("false").Bool()
|
||||
spsArgs.LoadBalanceMethod = sps.Flag("lb-method", "load balance method when use multiple parent,can be <roundrobin|leastconn|leasttime|hash|weight>").Default("hash").Enum("roundrobin", "weight", "leastconn", "leasttime", "hash")
|
||||
spsArgs.LoadBalanceTimeout = sps.Flag("lb-timeout", "tcp milliseconds timeout of connecting to parent").Default("500").Int()
|
||||
spsArgs.LoadBalanceRetryTime = sps.Flag("lb-retrytime", "sleep time milliseconds after checking").Default("1000").Int()
|
||||
spsArgs.LoadBalanceHashTarget = sps.Flag("lb-hashtarget", "use target address to choose parent for LB").Default("false").Bool()
|
||||
spsArgs.LoadBalanceOnlyHA = sps.Flag("lb-onlyha", "use only `high availability mode` to choose parent for LB").Default("false").Bool()
|
||||
spsArgs.RateLimit = sps.Flag("rate-limit", "rate limit (bytes/second) of each connection, such as: 100K 1.5M . 0 means no limitation").Short('l').Default("0").String()
|
||||
spsArgs.Jumper = sps.Flag("jumper", "https or socks5 proxies used when connecting to parent, only worked of -T is tls or tcp, format is https://username:password@host:port https://host:port or socks5://username:password@host:port socks5://host:port").Default("").String()
|
||||
spsArgs.ParentTLSSingle = sps.Flag("parent-tls-single", "conntect to parent insecure skip verify").Default("false").Bool()
|
||||
spsArgs.Debug = debug
|
||||
|
||||
//########dns#########
|
||||
dns := app.Command("dns", "proxy on dns server mode")
|
||||
dnsArgs.Parent = dns.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||
dnsArgs.CertFile = dns.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||
dnsArgs.KeyFile = dns.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||
dnsArgs.CaCertFile = dns.Flag("ca", "ca cert file for tls").Default("").String()
|
||||
dnsArgs.Timeout = dns.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||
dnsArgs.ParentType = dns.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||
dnsArgs.Local = dns.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":53").String()
|
||||
dnsArgs.ParentServiceType = dns.Flag("parent-service-type", "parent service type <http|socks>").Short('S').Enum("http", "socks")
|
||||
dnsArgs.RemoteDNSAddress = dns.Flag("dns-address", "remote dns for resolve doamin").Short('q').Default("8.8.8.8:53").String()
|
||||
dnsArgs.DNSTTL = dns.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||
dnsArgs.ParentAuth = dns.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||
dnsArgs.ParentKey = dns.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||
dnsArgs.ParentCompress = dns.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||
dnsArgs.CacheFile = dns.Flag("cache-file", "dns result cached file").Short('f').Default(filepath.Join(path.Dir(os.Args[0]), "cache.dat")).String()
|
||||
dnsArgs.LocalSocks5Port = dns.Flag("socks-port", "local socks5 port").Short('s').Default("65501").String()
|
||||
|
||||
//########keygen#########
|
||||
keygen := app.Command("keygen", "create certificate for proxy")
|
||||
keygenArgs.CommonName = keygen.Flag("cn", "common name").Short('n').Default("").String()
|
||||
keygenArgs.CaName = keygen.Flag("ca", "ca name").Short('C').Default("").String()
|
||||
keygenArgs.CertName = keygen.Flag("cert", "cert name of sign to create").Short('c').Default("").String()
|
||||
keygenArgs.SignDays = keygen.Flag("days", "days of sign").Short('d').Default("365").Int()
|
||||
keygenArgs.Sign = keygen.Flag("sign", "cert is to signin").Short('s').Default("false").Bool()
|
||||
|
||||
//parse args
|
||||
_args := strings.Fields(strings.Trim(serviceArgsStr, " "))
|
||||
args := []string{}
|
||||
for _, a := range _args {
|
||||
args = append(args, strings.Trim(a, "\""))
|
||||
}
|
||||
serviceName, err := app.Parse(args)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("parse args fail,err: %s", err)
|
||||
}
|
||||
//set kcp config
|
||||
|
||||
switch *kcpArgs.Mode {
|
||||
case "normal":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1
|
||||
case "fast":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1
|
||||
case "fast2":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1
|
||||
case "fast3":
|
||||
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1
|
||||
}
|
||||
pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New)
|
||||
|
||||
switch *kcpArgs.Crypt {
|
||||
case "sm4":
|
||||
kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||
case "tea":
|
||||
kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||
case "xor":
|
||||
kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||
case "none":
|
||||
kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||
case "aes-128":
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||
case "aes-192":
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||
case "blowfish":
|
||||
kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||
case "twofish":
|
||||
kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||
case "cast5":
|
||||
kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||
case "3des":
|
||||
kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||
case "xtea":
|
||||
kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||
case "salsa20":
|
||||
kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||
default:
|
||||
*kcpArgs.Crypt = "aes"
|
||||
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass)
|
||||
}
|
||||
//attach kcp config
|
||||
tcpArgs.KCP = kcpArgs
|
||||
httpArgs.KCP = kcpArgs
|
||||
socksArgs.KCP = kcpArgs
|
||||
spsArgs.KCP = kcpArgs
|
||||
muxBridgeArgs.KCP = kcpArgs
|
||||
muxServerArgs.KCP = kcpArgs
|
||||
muxClientArgs.KCP = kcpArgs
|
||||
dnsArgs.KCP = kcpArgs
|
||||
|
||||
log := logger.New(os.Stdout, "", logger.Ldate|logger.Ltime)
|
||||
flags := logger.Ldate
|
||||
if *debug {
|
||||
flags |= logger.Lshortfile | logger.Lmicroseconds
|
||||
} else {
|
||||
flags |= logger.Ltime
|
||||
}
|
||||
log.SetFlags(flags)
|
||||
|
||||
if loggerCallback == nil {
|
||||
if *nolog {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else if *logfile != "" {
|
||||
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
log.SetOutput(f)
|
||||
}
|
||||
} else {
|
||||
log.SetOutput(&logWriter{
|
||||
callback: loggerCallback,
|
||||
})
|
||||
}
|
||||
|
||||
//regist services and run service
|
||||
switch serviceName {
|
||||
case "http":
|
||||
services.Regist(serviceID, httpx.NewHTTP(), httpArgs, log)
|
||||
case "tcp":
|
||||
services.Regist(serviceID, tcpx.NewTCP(), tcpArgs, log)
|
||||
case "udp":
|
||||
services.Regist(serviceID, udpx.NewUDP(), udpArgs, log)
|
||||
case "tserver":
|
||||
services.Regist(serviceID, tunnelx.NewTunnelServerManager(), tunnelServerArgs, log)
|
||||
case "tclient":
|
||||
services.Regist(serviceID, tunnelx.NewTunnelClient(), tunnelClientArgs, log)
|
||||
case "tbridge":
|
||||
services.Regist(serviceID, tunnelx.NewTunnelBridge(), tunnelBridgeArgs, log)
|
||||
case "server":
|
||||
services.Regist(serviceID, mux.NewMuxServerManager(), muxServerArgs, log)
|
||||
case "client":
|
||||
services.Regist(serviceID, mux.NewMuxClient(), muxClientArgs, log)
|
||||
case "bridge":
|
||||
services.Regist(serviceID, mux.NewMuxBridge(), muxBridgeArgs, log)
|
||||
case "socks":
|
||||
services.Regist(serviceID, socksx.NewSocks(), socksArgs, log)
|
||||
case "sps":
|
||||
services.Regist(serviceID, spsx.NewSPS(), spsArgs, log)
|
||||
case "dns":
|
||||
services.Regist(serviceID, NewDNS(), dnsArgs, log)
|
||||
}
|
||||
_, err = services.Run(serviceID, nil)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("run service [%s:%s] fail, ERR:%s", serviceID, serviceName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Stop(serviceID string) {
|
||||
services.Stop(serviceID)
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
return SDK_VERSION
|
||||
}
|
||||
func StartProfiling(storePath string) {
|
||||
profilingLock.Lock()
|
||||
defer profilingLock.Unlock()
|
||||
if !isProfiling {
|
||||
isProfiling = true
|
||||
if storePath == "" {
|
||||
storePath = "."
|
||||
}
|
||||
cpuProfilingFile, _ = os.Create(filepath.Join(storePath, "cpu.prof"))
|
||||
memProfilingFile, _ = os.Create(filepath.Join(storePath, "memory.prof"))
|
||||
blockProfilingFile, _ = os.Create(filepath.Join(storePath, "block.prof"))
|
||||
goroutineProfilingFile, _ = os.Create(filepath.Join(storePath, "goroutine.prof"))
|
||||
threadcreateProfilingFile, _ = os.Create(filepath.Join(storePath, "threadcreate.prof"))
|
||||
pprof.StartCPUProfile(cpuProfilingFile)
|
||||
}
|
||||
}
|
||||
func StopProfiling() {
|
||||
profilingLock.Lock()
|
||||
defer profilingLock.Unlock()
|
||||
if isProfiling {
|
||||
isProfiling = false
|
||||
pprof.StopCPUProfile()
|
||||
goroutine := pprof.Lookup("goroutine")
|
||||
goroutine.WriteTo(goroutineProfilingFile, 1)
|
||||
heap := pprof.Lookup("heap")
|
||||
heap.WriteTo(memProfilingFile, 1)
|
||||
block := pprof.Lookup("block")
|
||||
block.WriteTo(blockProfilingFile, 1)
|
||||
threadcreate := pprof.Lookup("threadcreate")
|
||||
threadcreate.WriteTo(threadcreateProfilingFile, 1)
|
||||
//close
|
||||
goroutineProfilingFile.Close()
|
||||
memProfilingFile.Close()
|
||||
blockProfilingFile.Close()
|
||||
threadcreateProfilingFile.Close()
|
||||
}
|
||||
|
||||
}
|
||||
6
sdk/windows-linux/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
proxy-sdk.dll
|
||||
proxy-sdk.h
|
||||
proxy-sdk.so
|
||||
proxy-sdk.a
|
||||
*.tar.gz
|
||||
test.c
|
||||
BIN
sdk/windows-linux/ieshims.dll
Normal file
32
sdk/windows-linux/release_linux.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#/bin/bash
|
||||
VERSION=$(cat ../../VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X github.com/snail007/goproxy/sdk/android-ios.SDK_VERSION=$VER -X main.APP_VERSION=$VER"
|
||||
TRIMPATH1="/Users/snail/go/src/github.com/snail007"
|
||||
TRIMPATH=$(dirname ~/go/src/github.com/snail007)/snail007
|
||||
if [ -d "$TRIMPATH1" ];then
|
||||
TRIMPATH=$TRIMPATH1
|
||||
fi
|
||||
OPTS="-gcflags=-trimpath=$TRIMPATH -asmflags=-trimpath=$TRIMPATH"
|
||||
|
||||
rm -rf sdk-linux-*.tar.gz
|
||||
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||
|
||||
#linux 32bit
|
||||
CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-archive $OPTS -ldflags "-s -w $X" -o libproxy-sdk.a sdk.go
|
||||
CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-shared $OPTS -ldflags "-s -w $X" -o libproxy-sdk.so sdk.go
|
||||
cp ../README.md .
|
||||
tar zcf sdk-linux-32bit-${VERSION}.tar.gz README.md libproxy-sdk.so libproxy-sdk.a libproxy-sdk.h
|
||||
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||
|
||||
#linux 64bit
|
||||
CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive $OPTS -ldflags "-s -w $X" -o libproxy-sdk.a sdk.go
|
||||
CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared $OPTS -ldflags "-s -w $X" -o libproxy-sdk.so sdk.go
|
||||
cp ../README.md .
|
||||
tar zcf sdk-linux-64bit-${VERSION}.tar.gz README.md libproxy-sdk.so libproxy-sdk.a libproxy-sdk.h
|
||||
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||
|
||||
echo "done."
|
||||
|
||||
|
||||
|
||||
21
sdk/windows-linux/release_mac.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#/bin/bash
|
||||
VERSION=$(cat ../../VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X github.com/snail007/goproxy/sdk/android-ios.SDK_VERSION=$VER -X main.APP_VERSION=$VER"
|
||||
TRIMPATH1="/Users/snail/go/src/github.com/snail007"
|
||||
TRIMPATH=$(dirname ~/go/src/github.com/snail007)/snail007
|
||||
if [ -d "$TRIMPATH1" ];then
|
||||
TRIMPATH=$TRIMPATH1
|
||||
fi
|
||||
OPTS="-gcflags=-trimpath=$TRIMPATH -asmflags=-trimpath=$TRIMPATH"
|
||||
|
||||
rm -rf *.tar.gz
|
||||
rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||
|
||||
#mac , macos required
|
||||
CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -buildmode=c-shared $OPTS -ldflags "-s -w $X" -o libproxy-sdk.dylib sdk.go
|
||||
cp ../README.md .
|
||||
tar zcf sdk-mac-${VERSION}.tar.gz README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||
rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||
|
||||
echo "done."
|
||||
36
sdk/windows-linux/release_windows.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#/bin/bash
|
||||
VERSION=$(cat ../../VERSION)
|
||||
VER="${VERSION}_$(date '+%Y%m%d%H%M%S')"
|
||||
X="-X github.com/snail007/goproxy/sdk/android-ios.SDK_VERSION=$VER -X main.APP_VERSION=$VER"
|
||||
TRIMPATH1="/Users/snail/go/src/github.com/snail007"
|
||||
TRIMPATH=$(dirname ~/go/src/github.com/snail007)/snail007
|
||||
if [ -d "$TRIMPATH1" ];then
|
||||
TRIMPATH=$TRIMPATH1
|
||||
fi
|
||||
OPTS="-gcflags=-trimpath=$TRIMPATH -asmflags=-trimpath=$TRIMPATH"
|
||||
|
||||
#sudo rm /usr/local/go
|
||||
#sudo ln -s /usr/local/go1.10.1 /usr/local/go
|
||||
rm -rf sdk-windows-*.tar.gz
|
||||
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||
|
||||
|
||||
#apt-get install gcc-multilib
|
||||
#apt-get install gcc-mingw-w64
|
||||
|
||||
#windows 64bit
|
||||
CC=x86_64-w64-mingw32-gcc GOARCH=amd64 CGO_ENABLED=1 GOOS=windows go build $OPTS -buildmode=c-shared -ldflags "-s -w $X" -o proxy-sdk.dll sdk.go
|
||||
cp ../README.md .
|
||||
tar zcf sdk-windows-64bit-${VERSION}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll
|
||||
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||
|
||||
#windows 32bit
|
||||
CC=i686-w64-mingw32-gcc-win32 GOARCH=386 CGO_ENABLED=1 GOOS=windows go build $OPTS -buildmode=c-shared -ldflags "-s -w $X" -o proxy-sdk.dll sdk.go
|
||||
cp ../README.md .
|
||||
tar zcf sdk-windows-32bit-${VERSION}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll
|
||||
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||
|
||||
#sudo rm /usr/local/go
|
||||
#sudo ln -s /usr/local/go1.8.5 /usr/local/go
|
||||
|
||||
echo "done."
|
||||
35
sdk/windows-linux/sdk.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
|
||||
sdk "github.com/snail007/goproxy/sdk/android-ios"
|
||||
)
|
||||
|
||||
//export Start
|
||||
func Start(serviceID *C.char, serviceArgsStr *C.char) (errStr *C.char) {
|
||||
return C.CString(sdk.Start(C.GoString(serviceID), C.GoString(serviceArgsStr)))
|
||||
}
|
||||
|
||||
//export Stop
|
||||
func Stop(serviceID *C.char) {
|
||||
sdk.Stop(C.GoString(serviceID))
|
||||
}
|
||||
|
||||
//export Version
|
||||
func Version() (ver *C.char) {
|
||||
return C.CString(sdk.Version())
|
||||
}
|
||||
|
||||
//export StartProfiling
|
||||
func StartProfiling(storePath *C.char) {
|
||||
sdk.StartProfiling(C.GoString(storePath))
|
||||
}
|
||||
|
||||
//export StopProfiling
|
||||
func StopProfiling() {
|
||||
sdk.StopProfiling()
|
||||
}
|
||||
|
||||
func main() {
|
||||
}
|
||||
162
services/args.go
@ -1,162 +0,0 @@
|
||||
package services
|
||||
|
||||
import "golang.org/x/crypto/ssh"
|
||||
|
||||
// tcp := app.Command("tcp", "proxy on tcp mode")
|
||||
// t := tcp.Flag("tcp-timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
|
||||
|
||||
const (
|
||||
TYPE_TCP = "tcp"
|
||||
TYPE_UDP = "udp"
|
||||
TYPE_HTTP = "http"
|
||||
TYPE_TLS = "tls"
|
||||
TYPE_KCP = "kcp"
|
||||
CONN_CLIENT_CONTROL = uint8(1)
|
||||
CONN_CLIENT_HEARBEAT = uint8(2)
|
||||
CONN_SERVER_HEARBEAT = uint8(3)
|
||||
CONN_SERVER = uint8(4)
|
||||
CONN_CLIENT = uint8(5)
|
||||
CONN_SERVER_MUX = uint8(6)
|
||||
CONN_CLIENT_MUX = uint8(7)
|
||||
)
|
||||
|
||||
type TunnelServerArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
IsUDP *bool
|
||||
Key *string
|
||||
Remote *string
|
||||
Timeout *int
|
||||
Route *[]string
|
||||
Mgr *TunnelServerManager
|
||||
Mux *bool
|
||||
}
|
||||
type TunnelClientArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Key *string
|
||||
Timeout *int
|
||||
Mux *bool
|
||||
}
|
||||
type TunnelBridgeArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
Timeout *int
|
||||
Mux *bool
|
||||
}
|
||||
type TCPArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
ParentType *string
|
||||
LocalType *string
|
||||
Timeout *int
|
||||
PoolSize *int
|
||||
CheckParentInterval *int
|
||||
KCPMethod *string
|
||||
KCPKey *string
|
||||
}
|
||||
|
||||
type HTTPArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
Always *bool
|
||||
HTTPTimeout *int
|
||||
Interval *int
|
||||
Blocked *string
|
||||
Direct *string
|
||||
AuthFile *string
|
||||
Auth *[]string
|
||||
AuthURL *string
|
||||
AuthURLOkCode *int
|
||||
AuthURLTimeout *int
|
||||
AuthURLRetry *int
|
||||
ParentType *string
|
||||
LocalType *string
|
||||
Timeout *int
|
||||
PoolSize *int
|
||||
CheckParentInterval *int
|
||||
SSHKeyFile *string
|
||||
SSHKeyFileSalt *string
|
||||
SSHPassword *string
|
||||
SSHUser *string
|
||||
SSHKeyBytes []byte
|
||||
SSHAuthMethod ssh.AuthMethod
|
||||
KCPMethod *string
|
||||
KCPKey *string
|
||||
LocalIPS *[]string
|
||||
}
|
||||
type UDPArgs struct {
|
||||
Parent *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
ParentType *string
|
||||
Timeout *int
|
||||
PoolSize *int
|
||||
CheckParentInterval *int
|
||||
}
|
||||
type SocksArgs struct {
|
||||
Parent *string
|
||||
ParentType *string
|
||||
Local *string
|
||||
LocalType *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
SSHKeyFile *string
|
||||
SSHKeyFileSalt *string
|
||||
SSHPassword *string
|
||||
SSHUser *string
|
||||
SSHKeyBytes []byte
|
||||
SSHAuthMethod ssh.AuthMethod
|
||||
Timeout *int
|
||||
Always *bool
|
||||
Interval *int
|
||||
Blocked *string
|
||||
Direct *string
|
||||
AuthFile *string
|
||||
Auth *[]string
|
||||
AuthURL *string
|
||||
AuthURLOkCode *int
|
||||
AuthURLTimeout *int
|
||||
AuthURLRetry *int
|
||||
KCPMethod *string
|
||||
KCPKey *string
|
||||
UDPParent *string
|
||||
UDPLocal *string
|
||||
LocalIPS *[]string
|
||||
}
|
||||
|
||||
func (a *TCPArgs) Protocol() string {
|
||||
switch *a.LocalType {
|
||||
case TYPE_TLS:
|
||||
return TYPE_TLS
|
||||
case TYPE_TCP:
|
||||
return TYPE_TCP
|
||||
case TYPE_KCP:
|
||||
return TYPE_KCP
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
381
services/http.go
@ -1,381 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"proxy/utils"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type HTTP struct {
|
||||
outPool utils.OutPool
|
||||
cfg HTTPArgs
|
||||
checker utils.Checker
|
||||
basicAuth utils.BasicAuth
|
||||
sshClient *ssh.Client
|
||||
lockChn chan bool
|
||||
}
|
||||
|
||||
func NewHTTP() Service {
|
||||
return &HTTP{
|
||||
outPool: utils.OutPool{},
|
||||
cfg: HTTPArgs{},
|
||||
checker: utils.Checker{},
|
||||
basicAuth: utils.BasicAuth{},
|
||||
lockChn: make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
func (s *HTTP) CheckArgs() {
|
||||
var err error
|
||||
if *s.cfg.Parent != "" && *s.cfg.ParentType == "" {
|
||||
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
||||
}
|
||||
if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
if *s.cfg.SSHUser == "" {
|
||||
log.Fatalf("ssh user required")
|
||||
}
|
||||
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||
log.Fatalf("ssh password or key required")
|
||||
}
|
||||
|
||||
if *s.cfg.SSHPassword != "" {
|
||||
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||
} else {
|
||||
var SSHSigner ssh.Signer
|
||||
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("read key file ERR: %s", err)
|
||||
}
|
||||
if *s.cfg.SSHKeyFileSalt != "" {
|
||||
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||
} else {
|
||||
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
||||
}
|
||||
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *HTTP) InitService() {
|
||||
s.InitBasicAuth()
|
||||
if *s.cfg.Parent != "" {
|
||||
s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
err := s.ConnectSSH()
|
||||
if err != nil {
|
||||
log.Fatalf("init service fail, ERR: %s", err)
|
||||
}
|
||||
go func() {
|
||||
//循环检查ssh网络连通性
|
||||
for {
|
||||
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
||||
if err == nil {
|
||||
_, err = conn.Write([]byte{0})
|
||||
}
|
||||
if err != nil {
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
if s.sshClient.Conn != nil {
|
||||
s.sshClient.Conn.Close()
|
||||
}
|
||||
}
|
||||
log.Printf("ssh offline, retrying...")
|
||||
s.ConnectSSH()
|
||||
} else {
|
||||
conn.Close()
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
func (s *HTTP) StopService() {
|
||||
if s.outPool.Pool != nil {
|
||||
s.outPool.Pool.ReleaseAll()
|
||||
}
|
||||
}
|
||||
func (s *HTTP) Start(args interface{}) (err error) {
|
||||
s.cfg = args.(HTTPArgs)
|
||||
s.CheckArgs()
|
||||
if *s.cfg.Parent != "" {
|
||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||
s.InitOutConnPool()
|
||||
}
|
||||
s.InitService()
|
||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||
p, _ := strconv.Atoi(port)
|
||||
sc := utils.NewServerChannel(host, p)
|
||||
if *s.cfg.LocalType == TYPE_TCP {
|
||||
err = sc.ListenTCP(s.callback)
|
||||
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback)
|
||||
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTP) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *HTTP) callback(inConn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("http(s) conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
var err interface{}
|
||||
var req utils.HTTPRequest
|
||||
req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Printf("decoder error , from %s, ERR:%s", inConn.RemoteAddr(), err)
|
||||
}
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
address := req.Host
|
||||
|
||||
useProxy := true
|
||||
if *s.cfg.Parent == "" {
|
||||
useProxy = false
|
||||
} else if *s.cfg.Always {
|
||||
useProxy = true
|
||||
} else {
|
||||
if req.IsHTTPS() {
|
||||
s.checker.Add(address, true, req.Method, "", nil)
|
||||
} else {
|
||||
s.checker.Add(address, false, req.Method, req.URL, req.HeadBuf)
|
||||
}
|
||||
//var n, m uint
|
||||
useProxy, _, _ = s.checker.IsBlocked(req.Host)
|
||||
//log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
|
||||
}
|
||||
log.Printf("use proxy : %v, %s", useProxy, address)
|
||||
|
||||
err = s.OutToTCP(useProxy, address, &inConn, &req)
|
||||
|
||||
if err != nil {
|
||||
if *s.cfg.Parent == "" {
|
||||
log.Printf("connect to %s fail, ERR:%s", address, err)
|
||||
} else {
|
||||
log.Printf("connect to %s parent %s fail", *s.cfg.ParentType, *s.cfg.Parent)
|
||||
}
|
||||
utils.CloseConn(&inConn)
|
||||
}
|
||||
}
|
||||
func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *utils.HTTPRequest) (err interface{}) {
|
||||
inAddr := (*inConn).RemoteAddr().String()
|
||||
inLocalAddr := (*inConn).LocalAddr().String()
|
||||
//防止死循环
|
||||
if s.IsDeadLoop(inLocalAddr, req.Host) {
|
||||
utils.CloseConn(inConn)
|
||||
err = fmt.Errorf("dead loop detected , %s", req.Host)
|
||||
return
|
||||
}
|
||||
var outConn net.Conn
|
||||
var _outConn interface{}
|
||||
tryCount := 0
|
||||
maxTryCount := 5
|
||||
for {
|
||||
if useProxy {
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
outConn, err = s.getSSHConn(address)
|
||||
} else {
|
||||
//log.Printf("%v", s.outPool)
|
||||
_outConn, err = s.outPool.Pool.Get()
|
||||
if err == nil {
|
||||
outConn = _outConn.(net.Conn)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outConn, err = utils.ConnectHost(address, *s.cfg.Timeout)
|
||||
}
|
||||
tryCount++
|
||||
if err == nil || tryCount > maxTryCount {
|
||||
break
|
||||
} else {
|
||||
log.Printf("connect to %s , err:%s,retrying...", *s.cfg.Parent, err)
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
|
||||
outAddr := outConn.RemoteAddr().String()
|
||||
//outLocalAddr := outConn.LocalAddr().String()
|
||||
|
||||
if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") {
|
||||
//https无上级或者上级非代理,proxy需要响应connect请求,并直连目标
|
||||
err = req.HTTPSReply()
|
||||
} else {
|
||||
//https或者http,上级是代理,proxy需要转发
|
||||
_, err = outConn.Write(req.HeadBuf)
|
||||
if err != nil {
|
||||
log.Printf("write to %s , err:%s", *s.cfg.Parent, err)
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
||||
log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
|
||||
})
|
||||
log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) {
|
||||
maxTryCount := 1
|
||||
tryCount := 0
|
||||
RETRY:
|
||||
if tryCount >= maxTryCount {
|
||||
return
|
||||
}
|
||||
wait := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = recover()
|
||||
}
|
||||
wait <- true
|
||||
}()
|
||||
outConn, err = s.sshClient.Dial("tcp", host)
|
||||
}()
|
||||
select {
|
||||
case <-wait:
|
||||
case <-time.After(time.Second * 5):
|
||||
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||
e := s.ConnectSSH()
|
||||
if e == nil {
|
||||
tryCount++
|
||||
time.Sleep(time.Second * 3)
|
||||
goto RETRY
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) ConnectSSH() (err error) {
|
||||
select {
|
||||
case s.lockChn <- true:
|
||||
default:
|
||||
err = fmt.Errorf("can not connect at same time")
|
||||
return
|
||||
}
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: time.Duration(*s.cfg.Timeout) * time.Millisecond,
|
||||
User: *s.cfg.SSHUser,
|
||||
Auth: []ssh.AuthMethod{s.cfg.SSHAuthMethod},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
||||
<-s.lockChn
|
||||
return
|
||||
}
|
||||
func (s *HTTP) InitOutConnPool() {
|
||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
||||
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
||||
//parent string, timeout int, InitialCap int, MaxCap int
|
||||
s.outPool = utils.NewOutPool(
|
||||
*s.cfg.CheckParentInterval,
|
||||
*s.cfg.ParentType,
|
||||
*s.cfg.KCPMethod,
|
||||
*s.cfg.KCPKey,
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes,
|
||||
*s.cfg.Parent,
|
||||
*s.cfg.Timeout,
|
||||
*s.cfg.PoolSize,
|
||||
*s.cfg.PoolSize*2,
|
||||
)
|
||||
}
|
||||
}
|
||||
func (s *HTTP) InitBasicAuth() (err error) {
|
||||
s.basicAuth = utils.NewBasicAuth()
|
||||
if *s.cfg.AuthURL != "" {
|
||||
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||
log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||
}
|
||||
if *s.cfg.AuthFile != "" {
|
||||
var n = 0
|
||||
n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||
return
|
||||
}
|
||||
log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
if len(*s.cfg.Auth) > 0 {
|
||||
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||
log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) IsBasicAuth() bool {
|
||||
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||
}
|
||||
func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
|
||||
inIP, inPort, err := net.SplitHostPort(inLocalAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
outDomain, outPort, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if inPort == outPort {
|
||||
var outIPs []net.IP
|
||||
outIPs, err = net.LookupIP(outDomain)
|
||||
if err == nil {
|
||||
for _, ip := range outIPs {
|
||||
if ip.String() == inIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
interfaceIPs, err := utils.GetAllInterfaceAddr()
|
||||
for _, ip := range *s.cfg.LocalIPS {
|
||||
interfaceIPs = append(interfaceIPs, net.ParseIP(ip).To4())
|
||||
}
|
||||
if err == nil {
|
||||
for _, localIP := range interfaceIPs {
|
||||
for _, outIP := range outIPs {
|
||||
if localIP.Equal(outIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
681
services/http/http.go
Normal file
@ -0,0 +1,681 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
logger "log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
server "github.com/snail007/goproxy/core/cs/server"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
"github.com/snail007/goproxy/services"
|
||||
"github.com/snail007/goproxy/utils/datasize"
|
||||
"github.com/snail007/goproxy/utils/dnsx"
|
||||
"github.com/snail007/goproxy/utils/iolimiter"
|
||||
"github.com/snail007/goproxy/utils/jumper"
|
||||
"github.com/snail007/goproxy/utils/lb"
|
||||
"github.com/snail007/goproxy/utils/mapx"
|
||||
|
||||
"github.com/snail007/goproxy/utils"
|
||||
"github.com/snail007/goproxy/utils/conncrypt"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type HTTPArgs struct {
|
||||
Parent *[]string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CaCertFile *string
|
||||
CaCertBytes []byte
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
Always *bool
|
||||
HTTPTimeout *int
|
||||
Interval *int
|
||||
Blocked *string
|
||||
Direct *string
|
||||
AuthFile *string
|
||||
Auth *[]string
|
||||
AuthURL *string
|
||||
AuthURLOkCode *int
|
||||
AuthURLTimeout *int
|
||||
AuthURLRetry *int
|
||||
ParentType *string
|
||||
LocalType *string
|
||||
Timeout *int
|
||||
CheckParentInterval *int
|
||||
SSHKeyFile *string
|
||||
SSHKeyFileSalt *string
|
||||
SSHPassword *string
|
||||
SSHUser *string
|
||||
SSHKeyBytes []byte
|
||||
SSHAuthMethod ssh.AuthMethod
|
||||
KCP kcpcfg.KCPConfigArgs
|
||||
LocalIPS *[]string
|
||||
DNSAddress *string
|
||||
DNSTTL *int
|
||||
LocalKey *string
|
||||
ParentKey *string
|
||||
LocalCompress *bool
|
||||
ParentCompress *bool
|
||||
Intelligent *string
|
||||
LoadBalanceMethod *string
|
||||
LoadBalanceTimeout *int
|
||||
LoadBalanceRetryTime *int
|
||||
LoadBalanceHashTarget *bool
|
||||
LoadBalanceOnlyHA *bool
|
||||
|
||||
RateLimit *string
|
||||
RateLimitBytes float64
|
||||
BindListen *bool
|
||||
Debug *bool
|
||||
Jumper *string
|
||||
}
|
||||
type HTTP struct {
|
||||
cfg HTTPArgs
|
||||
checker utils.Checker
|
||||
basicAuth utils.BasicAuth
|
||||
sshClient *ssh.Client
|
||||
lockChn chan bool
|
||||
domainResolver dnsx.DomainResolver
|
||||
isStop bool
|
||||
serverChannels []*server.ServerChannel
|
||||
userConns mapx.ConcurrentMap
|
||||
log *logger.Logger
|
||||
lb *lb.Group
|
||||
jumper *jumper.Jumper
|
||||
}
|
||||
|
||||
func NewHTTP() services.Service {
|
||||
return &HTTP{
|
||||
cfg: HTTPArgs{},
|
||||
checker: utils.Checker{},
|
||||
basicAuth: utils.BasicAuth{},
|
||||
lockChn: make(chan bool, 1),
|
||||
isStop: false,
|
||||
serverChannels: []*server.ServerChannel{},
|
||||
userConns: mapx.NewConcurrentMap(),
|
||||
}
|
||||
}
|
||||
func (s *HTTP) CheckArgs() (err error) {
|
||||
|
||||
if len(*s.cfg.Parent) == 1 && (*s.cfg.Parent)[0] == "" {
|
||||
(*s.cfg.Parent) = []string{}
|
||||
}
|
||||
if len(*s.cfg.Parent) > 0 && *s.cfg.ParentType == "" {
|
||||
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|ssh|kcp>")
|
||||
return
|
||||
}
|
||||
if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if *s.cfg.CaCertFile != "" {
|
||||
s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read ca file error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
if *s.cfg.SSHUser == "" {
|
||||
err = fmt.Errorf("ssh user required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||
err = fmt.Errorf("ssh password or key required")
|
||||
return
|
||||
}
|
||||
|
||||
if *s.cfg.SSHPassword != "" {
|
||||
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||
} else {
|
||||
var SSHSigner ssh.Signer
|
||||
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read key file ERR: %s", err)
|
||||
return
|
||||
}
|
||||
if *s.cfg.SSHKeyFileSalt != "" {
|
||||
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||
} else {
|
||||
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse ssh private key fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||
}
|
||||
}
|
||||
if *s.cfg.RateLimit != "0" && *s.cfg.RateLimit != "" {
|
||||
var size uint64
|
||||
size, err = datasize.Parse(*s.cfg.RateLimit)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse rate limit size error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
s.cfg.RateLimitBytes = float64(size)
|
||||
}
|
||||
if *s.cfg.Jumper != "" {
|
||||
if *s.cfg.ParentType != "tls" && *s.cfg.ParentType != "tcp" {
|
||||
err = fmt.Errorf("jumper only worked of -T is tls or tcp")
|
||||
return
|
||||
}
|
||||
var j jumper.Jumper
|
||||
j, err = jumper.New(*s.cfg.Jumper, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse jumper fail, err %s", err)
|
||||
return
|
||||
}
|
||||
s.jumper = &j
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) InitService() (err error) {
|
||||
s.InitBasicAuth()
|
||||
//init lb
|
||||
if len(*s.cfg.Parent) > 0 {
|
||||
s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct, s.log, *s.cfg.Intelligent)
|
||||
s.InitLB()
|
||||
}
|
||||
if *s.cfg.DNSAddress != "" {
|
||||
s.domainResolver = dnsx.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
err = s.ConnectSSH()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("init service fail, ERR: %s", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
//循环检查ssh网络连通性
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
conn, err := utils.ConnectHost(s.Resolve(s.lb.Select("", *s.cfg.LoadBalanceOnlyHA)), *s.cfg.Timeout*2)
|
||||
if err == nil && conn != nil {
|
||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
_, err = conn.Write([]byte{0})
|
||||
conn.SetDeadline(time.Time{})
|
||||
}
|
||||
if err != nil {
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
if s.sshClient.Conn != nil {
|
||||
s.sshClient.Conn.Close()
|
||||
}
|
||||
}
|
||||
s.log.Printf("ssh offline, retrying...")
|
||||
s.ConnectSSH()
|
||||
} else {
|
||||
conn.Close()
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) StopService() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stop http(s) service crashed,%s", e)
|
||||
} else {
|
||||
s.log.Printf("service http(s) stopped")
|
||||
}
|
||||
s.basicAuth = utils.BasicAuth{}
|
||||
s.cfg = HTTPArgs{}
|
||||
s.checker = utils.Checker{}
|
||||
s.domainResolver = dnsx.DomainResolver{}
|
||||
s.lb = nil
|
||||
s.lockChn = nil
|
||||
s.log = nil
|
||||
s.jumper = nil
|
||||
s.serverChannels = nil
|
||||
s.sshClient = nil
|
||||
s.userConns = nil
|
||||
s = nil
|
||||
}()
|
||||
s.isStop = true
|
||||
if len(*s.cfg.Parent) > 0 {
|
||||
s.checker.Stop()
|
||||
}
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
for _, sc := range s.serverChannels {
|
||||
if sc.Listener != nil && *sc.Listener != nil {
|
||||
(*sc.Listener).Close()
|
||||
}
|
||||
if sc.UDPListener != nil {
|
||||
(*sc.UDPListener).Close()
|
||||
}
|
||||
}
|
||||
if s.lb != nil {
|
||||
s.lb.Stop()
|
||||
}
|
||||
}
|
||||
func (s *HTTP) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(HTTPArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(*s.cfg.Parent) > 0 {
|
||||
s.log.Printf("use %s parent %v [ %s ]", *s.cfg.ParentType, *s.cfg.Parent, strings.ToUpper(*s.cfg.LoadBalanceMethod))
|
||||
}
|
||||
|
||||
for _, addr := range strings.Split(*s.cfg.Local, ",") {
|
||||
if addr != "" {
|
||||
host, port, _ := net.SplitHostPort(addr)
|
||||
p, _ := strconv.Atoi(port)
|
||||
sc := server.NewServerChannel(host, p, s.log)
|
||||
if *s.cfg.LocalType == "tcp" {
|
||||
err = sc.ListenTCP(s.callback)
|
||||
} else if *s.cfg.LocalType == "tls" {
|
||||
err = sc.ListenTLS(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.callback)
|
||||
} else if *s.cfg.LocalType == "kcp" {
|
||||
err = sc.ListenKCP(s.cfg.KCP, s.callback, s.log)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||
s.serverChannels = append(s.serverChannels, &sc)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTP) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *HTTP) callback(inConn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
s.log.Printf("http(s) conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if *s.cfg.LocalCompress {
|
||||
inConn = utils.NewCompConn(inConn)
|
||||
}
|
||||
if *s.cfg.LocalKey != "" {
|
||||
inConn = conncrypt.New(inConn, &conncrypt.Config{
|
||||
Password: *s.cfg.LocalKey,
|
||||
})
|
||||
}
|
||||
var err interface{}
|
||||
var req utils.HTTPRequest
|
||||
req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth, s.log)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
s.log.Printf("decoder error , from %s, ERR:%s", inConn.RemoteAddr(), err)
|
||||
}
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
address := req.Host
|
||||
host, _, _ := net.SplitHostPort(address)
|
||||
useProxy := false
|
||||
if !utils.IsInternalIP(host, *s.cfg.Always) {
|
||||
useProxy = true
|
||||
if len(*s.cfg.Parent) == 0 {
|
||||
useProxy = false
|
||||
} else if *s.cfg.Always {
|
||||
useProxy = true
|
||||
} else {
|
||||
var isInMap bool
|
||||
useProxy, isInMap, _, _ = s.checker.IsBlocked(address)
|
||||
if !isInMap {
|
||||
s.checker.Add(address, s.Resolve(address))
|
||||
}
|
||||
//s.log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
|
||||
}
|
||||
}
|
||||
|
||||
s.log.Printf("use proxy : %v, %s", useProxy, address)
|
||||
|
||||
lbAddr, err := s.OutToTCP(useProxy, address, &inConn, &req)
|
||||
if err != nil {
|
||||
if len(*s.cfg.Parent) == 0 {
|
||||
s.log.Printf("connect to %s fail, ERR:%s", address, err)
|
||||
} else {
|
||||
s.log.Printf("connect to %s parent %v fail", *s.cfg.ParentType, lbAddr)
|
||||
}
|
||||
utils.CloseConn(&inConn)
|
||||
}
|
||||
}
|
||||
func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *utils.HTTPRequest) (lbAddr string, err interface{}) {
|
||||
inAddr := (*inConn).RemoteAddr().String()
|
||||
inLocalAddr := (*inConn).LocalAddr().String()
|
||||
//防止死循环
|
||||
if s.IsDeadLoop(inLocalAddr, req.Host) {
|
||||
utils.CloseConn(inConn)
|
||||
err = fmt.Errorf("dead loop detected , %s", req.Host)
|
||||
return
|
||||
}
|
||||
var outConn net.Conn
|
||||
tryCount := 0
|
||||
maxTryCount := 5
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
if useProxy {
|
||||
// s.log.Printf("%v", s.outPool)
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
outConn, err = s.getSSHConn(address)
|
||||
} else {
|
||||
selectAddr := (*inConn).RemoteAddr().String()
|
||||
if utils.LBMethod(*s.cfg.LoadBalanceMethod) == lb.SELECT_HASH && *s.cfg.LoadBalanceHashTarget {
|
||||
selectAddr = address
|
||||
}
|
||||
lbAddr = s.lb.Select(selectAddr, *s.cfg.LoadBalanceOnlyHA)
|
||||
outConn, err = s.GetParentConn(lbAddr)
|
||||
}
|
||||
|
||||
} else {
|
||||
outConn, err = s.GetDirectConn(s.Resolve(address), inLocalAddr)
|
||||
}
|
||||
tryCount++
|
||||
if err == nil || tryCount > maxTryCount {
|
||||
break
|
||||
} else {
|
||||
s.log.Printf("connect to %s , err:%s,retrying...", lbAddr, err)
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
s.log.Printf("connect to %s , err:%s", lbAddr, err)
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
if *s.cfg.ParentCompress {
|
||||
outConn = utils.NewCompConn(outConn)
|
||||
}
|
||||
if useProxy && *s.cfg.ParentKey != "" {
|
||||
outConn = conncrypt.New(outConn, &conncrypt.Config{
|
||||
Password: *s.cfg.ParentKey,
|
||||
})
|
||||
}
|
||||
|
||||
outAddr := outConn.RemoteAddr().String()
|
||||
//outLocalAddr := outConn.LocalAddr().String()
|
||||
if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") {
|
||||
//https无上级或者上级非代理,proxy需要响应connect请求,并直连目标
|
||||
err = req.HTTPSReply()
|
||||
} else {
|
||||
//https或者http,上级是代理,proxy需要转发
|
||||
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
//直连目标或上级非代理或非SNI,,清理HTTP头部的代理头信息
|
||||
if (!useProxy || *s.cfg.ParentType == "ssh") && !req.IsSNI {
|
||||
_, err = outConn.Write(utils.RemoveProxyHeaders(req.HeadBuf))
|
||||
} else {
|
||||
_, err = outConn.Write(req.HeadBuf)
|
||||
}
|
||||
outConn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("write to %s , err:%s", lbAddr, err)
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s.cfg.RateLimitBytes > 0 {
|
||||
outConn = iolimiter.NewReaderConn(outConn, s.cfg.RateLimitBytes)
|
||||
}
|
||||
|
||||
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
||||
s.log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
|
||||
s.userConns.Remove(inAddr)
|
||||
if len(*s.cfg.Parent) > 0 {
|
||||
s.lb.DecreaseConns(lbAddr)
|
||||
}
|
||||
}, s.log)
|
||||
s.log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host)
|
||||
if c, ok := s.userConns.Get(inAddr); ok {
|
||||
(*c.(*net.Conn)).Close()
|
||||
}
|
||||
s.userConns.Set(inAddr, inConn)
|
||||
if len(*s.cfg.Parent) > 0 {
|
||||
s.lb.IncreasConns(lbAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) {
|
||||
maxTryCount := 1
|
||||
tryCount := 0
|
||||
RETRY:
|
||||
if tryCount >= maxTryCount || s.isStop {
|
||||
return
|
||||
}
|
||||
wait := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = recover()
|
||||
}
|
||||
wait <- true
|
||||
}()
|
||||
outConn, err = s.sshClient.Dial("tcp", host)
|
||||
}()
|
||||
select {
|
||||
case <-wait:
|
||||
case <-time.After(time.Second * 5):
|
||||
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||
}
|
||||
if err != nil {
|
||||
s.log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||
e := s.ConnectSSH()
|
||||
if e == nil {
|
||||
tryCount++
|
||||
time.Sleep(time.Second * 3)
|
||||
goto RETRY
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) ConnectSSH() (err error) {
|
||||
select {
|
||||
case s.lockChn <- true:
|
||||
default:
|
||||
err = fmt.Errorf("can not connect at same time")
|
||||
return
|
||||
}
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: time.Duration(*s.cfg.Timeout) * time.Millisecond,
|
||||
User: *s.cfg.SSHUser,
|
||||
Auth: []ssh.AuthMethod{s.cfg.SSHAuthMethod},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
s.sshClient, err = ssh.Dial("tcp", s.Resolve(s.lb.Select("", *s.cfg.LoadBalanceOnlyHA)), &config)
|
||||
if err != nil {
|
||||
s.log.Printf("connect to ssh %s fail", s.cfg.Parent)
|
||||
}
|
||||
<-s.lockChn
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTP) InitBasicAuth() (err error) {
|
||||
if *s.cfg.DNSAddress != "" {
|
||||
s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver, s.log)
|
||||
} else {
|
||||
s.basicAuth = utils.NewBasicAuth(nil, s.log)
|
||||
}
|
||||
if *s.cfg.AuthURL != "" {
|
||||
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||
s.log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||
}
|
||||
if *s.cfg.AuthFile != "" {
|
||||
var n = 0
|
||||
n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||
return
|
||||
}
|
||||
s.log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
if len(*s.cfg.Auth) > 0 {
|
||||
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||
s.log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) InitLB() {
|
||||
configs := lb.BackendsConfig{}
|
||||
for _, addr := range *s.cfg.Parent {
|
||||
_addrInfo := strings.Split(addr, "@")
|
||||
_addr := _addrInfo[0]
|
||||
weight := 1
|
||||
if len(_addrInfo) == 2 {
|
||||
weight, _ = strconv.Atoi(_addrInfo[1])
|
||||
}
|
||||
configs = append(configs, &lb.BackendConfig{
|
||||
Address: _addr,
|
||||
Weight: weight,
|
||||
ActiveAfter: 1,
|
||||
InactiveAfter: 2,
|
||||
Timeout: time.Duration(*s.cfg.LoadBalanceTimeout) * time.Millisecond,
|
||||
RetryTime: time.Duration(*s.cfg.LoadBalanceRetryTime) * time.Millisecond,
|
||||
})
|
||||
}
|
||||
LB := lb.NewGroup(utils.LBMethod(*s.cfg.LoadBalanceMethod), configs, &s.domainResolver, s.log, *s.cfg.Debug)
|
||||
s.lb = &LB
|
||||
}
|
||||
func (s *HTTP) IsBasicAuth() bool {
|
||||
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||
}
|
||||
func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
|
||||
inIP, inPort, err := net.SplitHostPort(inLocalAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
outDomain, outPort, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if inPort == outPort {
|
||||
var outIPs []net.IP
|
||||
if *s.cfg.DNSAddress != "" {
|
||||
outIPs = []net.IP{net.ParseIP(s.Resolve(outDomain))}
|
||||
} else {
|
||||
outIPs, err = utils.LookupIP(outDomain)
|
||||
}
|
||||
if err == nil {
|
||||
for _, ip := range outIPs {
|
||||
if ip.String() == inIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
interfaceIPs, err := utils.GetAllInterfaceAddr()
|
||||
for _, ip := range *s.cfg.LocalIPS {
|
||||
interfaceIPs = append(interfaceIPs, net.ParseIP(ip).To4())
|
||||
}
|
||||
if err == nil {
|
||||
for _, localIP := range interfaceIPs {
|
||||
for _, outIP := range outIPs {
|
||||
if localIP.Equal(outIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s *HTTP) Resolve(address string) string {
|
||||
if *s.cfg.DNSAddress == "" {
|
||||
return address
|
||||
}
|
||||
ip, err := s.domainResolver.Resolve(address)
|
||||
if err != nil {
|
||||
s.log.Printf("dns error %s , ERR:%s", address, err)
|
||||
return address
|
||||
}
|
||||
return ip
|
||||
}
|
||||
func (s *HTTP) GetParentConn(address string) (conn net.Conn, err error) {
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
if s.jumper == nil {
|
||||
var _conn tls.Conn
|
||||
_conn, err = utils.TlsConnectHost(address, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes)
|
||||
if err == nil {
|
||||
conn = net.Conn(&_conn)
|
||||
}
|
||||
} else {
|
||||
conf, err := utils.TlsConfig(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var _c net.Conn
|
||||
_c, err = s.jumper.Dial(address, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err == nil {
|
||||
conn = net.Conn(tls.Client(_c, conf))
|
||||
}
|
||||
}
|
||||
|
||||
} else if *s.cfg.ParentType == "kcp" {
|
||||
conn, err = utils.ConnectKCPHost(address, s.cfg.KCP)
|
||||
} else if *s.cfg.ParentType == "ssh" {
|
||||
var e interface{}
|
||||
conn, e = s.getSSHConn(address)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("%s", e)
|
||||
}
|
||||
} else {
|
||||
if s.jumper == nil {
|
||||
conn, err = utils.ConnectHost(address, *s.cfg.Timeout)
|
||||
} else {
|
||||
conn, err = s.jumper.Dial(address, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *HTTP) GetDirectConn(address string, localAddr string) (conn net.Conn, err error) {
|
||||
if !*s.cfg.BindListen {
|
||||
return utils.ConnectHost(address, *s.cfg.Timeout)
|
||||
}
|
||||
ip, _, _ := net.SplitHostPort(localAddr)
|
||||
if utils.IsInternalIP(ip, false) {
|
||||
return utils.ConnectHost(address, *s.cfg.Timeout)
|
||||
}
|
||||
local, _ := net.ResolveTCPAddr("tcp", ip+":0")
|
||||
d := net.Dialer{
|
||||
Timeout: time.Millisecond * time.Duration(*s.cfg.Timeout),
|
||||
LocalAddr: local,
|
||||
}
|
||||
conn, err = d.Dial("tcp", address)
|
||||
return
|
||||
}
|
||||
24
services/kcpcfg/args.go
Normal file
@ -0,0 +1,24 @@
|
||||
package kcpcfg
|
||||
|
||||
import kcp "github.com/xtaci/kcp-go"
|
||||
|
||||
type KCPConfigArgs struct {
|
||||
Key *string
|
||||
Crypt *string
|
||||
Mode *string
|
||||
MTU *int
|
||||
SndWnd *int
|
||||
RcvWnd *int
|
||||
DataShard *int
|
||||
ParityShard *int
|
||||
DSCP *int
|
||||
NoComp *bool
|
||||
AckNodelay *bool
|
||||
NoDelay *int
|
||||
Interval *int
|
||||
Resend *int
|
||||
NoCongestion *int
|
||||
SockBuf *int
|
||||
KeepAlive *int
|
||||
Block kcp.BlockCrypt
|
||||
}
|
||||
71
services/keygen/keygen.go
Normal file
@ -0,0 +1,71 @@
|
||||
package keygen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
logger "log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/snail007/goproxy/services"
|
||||
"github.com/snail007/goproxy/utils"
|
||||
"github.com/snail007/goproxy/utils/cert"
|
||||
)
|
||||
|
||||
type KeygenArgs struct {
|
||||
CaName *string
|
||||
CertName *string
|
||||
Sign *bool
|
||||
SignDays *int
|
||||
CommonName *string
|
||||
}
|
||||
|
||||
type Keygen struct {
|
||||
cfg KeygenArgs
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func NewKeygen() services.Service {
|
||||
return &Keygen{}
|
||||
}
|
||||
func (s *Keygen) CheckArgs() (err error) {
|
||||
if *s.cfg.Sign && (*s.cfg.CertName == "" || *s.cfg.CaName == "") {
|
||||
err = fmt.Errorf("ca name and cert name required for signin")
|
||||
return
|
||||
}
|
||||
if !*s.cfg.Sign && *s.cfg.CaName == "" {
|
||||
err = fmt.Errorf("ca name required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.CommonName == "" {
|
||||
domainSubfixList := []string{".com", ".edu", ".gov", ".int", ".mil", ".net", ".org", ".biz", ".info", ".pro", ".name", ".museum", ".coop", ".aero", ".xxx", ".idv", ".ac", ".ad", ".ae", ".af", ".ag", ".ai", ".al", ".am", ".an", ".ao", ".aq", ".ar", ".as", ".at", ".au", ".aw", ".az", ".ba", ".bb", ".bd", ".be", ".bf", ".bg", ".bh", ".bi", ".bj", ".bm", ".bn", ".bo", ".br", ".bs", ".bt", ".bv", ".bw", ".by", ".bz", ".ca", ".cc", ".cd", ".cf", ".cg", ".ch", ".ci", ".ck", ".cl", ".cm", ".cn", ".co", ".cr", ".cu", ".cv", ".cx", ".cy", ".cz", ".de", ".dj", ".dk", ".dm", ".do", ".dz", ".ec", ".ee", ".eg", ".eh", ".er", ".es", ".et", ".eu", ".fi", ".fj", ".fk", ".fm", ".fo", ".fr", ".ga", ".gd", ".ge", ".gf", ".gg", ".gh", ".gi", ".gl", ".gm", ".gn", ".gp", ".gq", ".gr", ".gs", ".gt", ".gu", ".gw", ".gy", ".hk", ".hm", ".hn", ".hr", ".ht", ".hu", ".id", ".ie", ".il", ".im", ".in", ".io", ".iq", ".ir", ".is", ".it", ".je", ".jm", ".jo", ".jp", ".ke", ".kg", ".kh", ".ki", ".km", ".kn", ".kp", ".kr", ".kw", ".ky", ".kz", ".la", ".lb", ".lc", ".li", ".lk", ".lr", ".ls", ".lt", ".lu", ".lv", ".ly", ".ma", ".mc", ".md", ".mg", ".mh", ".mk", ".ml", ".mm", ".mn", ".mo", ".mp", ".mq", ".mr", ".ms", ".mt", ".mu", ".mv", ".mw", ".mx", ".my", ".mz", ".na", ".nc", ".ne", ".nf", ".ng", ".ni", ".nl", ".no", ".np", ".nr", ".nu", ".nz", ".om", ".pa", ".pe", ".pf", ".pg", ".ph", ".pk", ".pl", ".pm", ".pn", ".pr", ".ps", ".pt", ".pw", ".py", ".qa", ".re", ".ro", ".ru", ".rw", ".sa", ".sb", ".sc", ".sd", ".se", ".sg", ".sh", ".si", ".sj", ".sk", ".sl", ".sm", ".sn", ".so", ".sr", ".st", ".sv", ".sy", ".sz", ".tc", ".td", ".tf", ".tg", ".th", ".tj", ".tk", ".tl", ".tm", ".tn", ".to", ".tp", ".tr", ".tt", ".tv", ".tw", ".tz", ".ua", ".ug", ".uk", ".um", ".us", ".uy", ".uz", ".va", ".vc", ".ve", ".vg", ".vi", ".vn", ".vu", ".wf", ".ws", ".ye", ".yt", ".yu", ".yr", ".za", ".zm", ".zw"}
|
||||
CN := strings.ToLower(utils.RandString(int(utils.RandInt(4)%10)) + domainSubfixList[int(utils.RandInt(4))%len(domainSubfixList)])
|
||||
*s.cfg.CommonName = CN
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *Keygen) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(KeygenArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if *s.cfg.Sign {
|
||||
caCert, caKey, err := cert.ParseCertAndKey(*s.cfg.CaName+".crt", *s.cfg.CaName+".key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cert.CreateSignCertToFile(caCert, caKey, *s.cfg.CommonName, *s.cfg.SignDays, *s.cfg.CertName)
|
||||
} else {
|
||||
err = cert.CreateCaToFile(*s.cfg.CaName, *s.cfg.CommonName, *s.cfg.SignDays)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.log.Println("success")
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Keygen) Clean() {
|
||||
|
||||
}
|
||||
342
services/mux/mux_bridge.go
Normal file
@ -0,0 +1,342 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
logger "log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
srvtransport "github.com/snail007/goproxy/core/cs/server"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
"github.com/snail007/goproxy/services"
|
||||
"github.com/snail007/goproxy/utils"
|
||||
"github.com/snail007/goproxy/utils/mapx"
|
||||
//"github.com/xtaci/smux"
|
||||
smux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
type MuxBridgeArgs struct {
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
LocalType *string
|
||||
Timeout *int
|
||||
IsCompress *bool
|
||||
KCP kcpcfg.KCPConfigArgs
|
||||
TCPSMethod *string
|
||||
TCPSPassword *string
|
||||
TOUMethod *string
|
||||
TOUPassword *string
|
||||
}
|
||||
type MuxBridge struct {
|
||||
cfg MuxBridgeArgs
|
||||
clientControlConns mapx.ConcurrentMap
|
||||
serverConns mapx.ConcurrentMap
|
||||
router utils.ClientKeyRouter
|
||||
l *sync.Mutex
|
||||
isStop bool
|
||||
sc *srvtransport.ServerChannel
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func NewMuxBridge() services.Service {
|
||||
b := &MuxBridge{
|
||||
cfg: MuxBridgeArgs{},
|
||||
clientControlConns: mapx.NewConcurrentMap(),
|
||||
serverConns: mapx.NewConcurrentMap(),
|
||||
l: &sync.Mutex{},
|
||||
isStop: false,
|
||||
}
|
||||
b.router = utils.NewClientKeyRouter(&b.clientControlConns, 50000)
|
||||
return b
|
||||
}
|
||||
|
||||
func (s *MuxBridge) InitService() (err error) {
|
||||
return
|
||||
}
|
||||
func (s *MuxBridge) CheckArgs() (err error) {
|
||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||
err = fmt.Errorf("cert and key file required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.LocalType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxBridge) StopService() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stop bridge service crashed,%s", e)
|
||||
} else {
|
||||
s.log.Printf("service bridge stopped")
|
||||
}
|
||||
s.cfg = MuxBridgeArgs{}
|
||||
s.clientControlConns = nil
|
||||
s.l = nil
|
||||
s.log = nil
|
||||
s.router = utils.ClientKeyRouter{}
|
||||
s.sc = nil
|
||||
s.serverConns = nil
|
||||
s = nil
|
||||
}()
|
||||
s.isStop = true
|
||||
if s.sc != nil && (*s.sc).Listener != nil {
|
||||
(*(*s.sc).Listener).Close()
|
||||
}
|
||||
for _, g := range s.clientControlConns.Items() {
|
||||
for _, session := range g.(*mapx.ConcurrentMap).Items() {
|
||||
(session.(*smux.Session)).Close()
|
||||
}
|
||||
}
|
||||
for _, c := range s.serverConns.Items() {
|
||||
(*c.(*net.Conn)).Close()
|
||||
}
|
||||
}
|
||||
func (s *MuxBridge) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(MuxBridgeArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sc := srvtransport.NewServerChannelHost(*s.cfg.Local, s.log)
|
||||
if *s.cfg.LocalType == "tcp" {
|
||||
err = sc.ListenTCP(s.handler)
|
||||
} else if *s.cfg.LocalType == "tls" {
|
||||
err = sc.ListenTLS(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.handler)
|
||||
} else if *s.cfg.LocalType == "kcp" {
|
||||
err = sc.ListenKCP(s.cfg.KCP, s.handler, s.log)
|
||||
} else if *s.cfg.LocalType == "tcps" {
|
||||
err = sc.ListenTCPS(*s.cfg.TCPSMethod, *s.cfg.TCPSPassword, false, s.handler)
|
||||
} else if *s.cfg.LocalType == "tou" {
|
||||
err = sc.ListenTOU(*s.cfg.TOUMethod, *s.cfg.TOUPassword, false, s.handler)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sc = &sc
|
||||
if *s.cfg.LocalType == "tou" {
|
||||
s.log.Printf("%s bridge on %s", *s.cfg.LocalType, sc.UDPListener.LocalAddr())
|
||||
} else {
|
||||
s.log.Printf("%s bridge on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxBridge) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *MuxBridge) handler(inConn net.Conn) {
|
||||
reader := bufio.NewReader(inConn)
|
||||
|
||||
var err error
|
||||
var connType uint8
|
||||
var key string
|
||||
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
err = utils.ReadPacket(reader, &connType, &key)
|
||||
inConn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("read error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
switch connType {
|
||||
case CONN_SERVER:
|
||||
var serverID string
|
||||
inAddr := inConn.RemoteAddr().String()
|
||||
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
err = utils.ReadPacketData(reader, &serverID)
|
||||
inConn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("read error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
s.log.Printf("server connection %s %s connected", serverID, key)
|
||||
if c, ok := s.serverConns.Get(inAddr); ok {
|
||||
(*c.(*net.Conn)).Close()
|
||||
}
|
||||
s.serverConns.Set(inAddr, &inConn)
|
||||
session, err := smux.Server(inConn, nil)
|
||||
if err != nil {
|
||||
utils.CloseConn(&inConn)
|
||||
s.log.Printf("server session error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
utils.CloseConn(&inConn)
|
||||
s.serverConns.Remove(inAddr)
|
||||
s.log.Printf("server connection %s %s released", serverID, key)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
s.log.Printf("bridge callback crashed,err: %s", e)
|
||||
}
|
||||
}()
|
||||
s.callback(stream, serverID, key)
|
||||
}()
|
||||
}
|
||||
case CONN_CLIENT:
|
||||
s.log.Printf("client connection %s connected", key)
|
||||
session, err := smux.Client(inConn, nil)
|
||||
if err != nil {
|
||||
utils.CloseConn(&inConn)
|
||||
s.log.Printf("client session error,ERR:%s", err)
|
||||
return
|
||||
}
|
||||
keyInfo := strings.Split(key, "-")
|
||||
if len(keyInfo) != 2 {
|
||||
utils.CloseConn(&inConn)
|
||||
s.log.Printf("client key format error,key:%s", key)
|
||||
return
|
||||
}
|
||||
groupKey := keyInfo[0]
|
||||
index := keyInfo[1]
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
var group *mapx.ConcurrentMap
|
||||
if !s.clientControlConns.Has(groupKey) {
|
||||
_g := mapx.NewConcurrentMap()
|
||||
group = &_g
|
||||
s.clientControlConns.Set(groupKey, group)
|
||||
//s.log.Printf("init client session group %s", groupKey)
|
||||
} else {
|
||||
_group, _ := s.clientControlConns.Get(groupKey)
|
||||
group = _group.(*mapx.ConcurrentMap)
|
||||
}
|
||||
if v, ok := group.Get(index); ok {
|
||||
v.(*smux.Session).Close()
|
||||
}
|
||||
group.Set(index, session)
|
||||
//s.log.Printf("set client session %s to group %s,grouplen:%d", index, groupKey, group.Count())
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
if session.IsClosed() {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
if sess, ok := group.Get(index); ok && sess.(*smux.Session).IsClosed() {
|
||||
group.Remove(index)
|
||||
//s.log.Printf("client session %s removed from group %s, grouplen:%d", key, groupKey, group.Count())
|
||||
s.log.Printf("client connection %s released", key)
|
||||
}
|
||||
if group.IsEmpty() {
|
||||
s.clientControlConns.Remove(groupKey)
|
||||
//s.log.Printf("client session group %s removed", groupKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
//s.log.Printf("set client session,key: %s", key)
|
||||
}
|
||||
|
||||
}
|
||||
func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) {
|
||||
try := 20
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
try--
|
||||
if try == 0 {
|
||||
break
|
||||
}
|
||||
if key == "*" {
|
||||
key = s.router.GetKey()
|
||||
}
|
||||
//s.log.Printf("server get client session %s", key)
|
||||
_group, ok := s.clientControlConns.Get(key)
|
||||
if !ok {
|
||||
s.log.Printf("client %s session not exists for server stream %s, retrying...", key, serverID)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
group := _group.(*mapx.ConcurrentMap)
|
||||
keys := []string{}
|
||||
group.IterCb(func(key string, v interface{}) {
|
||||
keys = append(keys, key)
|
||||
})
|
||||
keysLen := len(keys)
|
||||
//s.log.Printf("client session %s , len:%d , keysLen: %d", key, group.Count(), keysLen)
|
||||
i := 0
|
||||
if keysLen > 0 {
|
||||
i = rand.Intn(keysLen)
|
||||
} else {
|
||||
s.log.Printf("client %s session empty for server stream %s, retrying...", key, serverID)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
index := keys[i]
|
||||
s.log.Printf("select client : %s-%s", key, index)
|
||||
session, _ := group.Get(index)
|
||||
//session.(*smux.Session).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
stream, err := session.(*smux.Session).OpenStream()
|
||||
//session.(*smux.Session).SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
} else {
|
||||
s.log.Printf("stream %s -> %s created", serverID, key)
|
||||
die1 := make(chan bool, 1)
|
||||
die2 := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(stream, inConn)
|
||||
die1 <- true
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(inConn, stream)
|
||||
die2 <- true
|
||||
}()
|
||||
select {
|
||||
case <-die1:
|
||||
case <-die2:
|
||||
}
|
||||
stream.Close()
|
||||
inConn.Close()
|
||||
s.log.Printf("%s server %s stream released", key, serverID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
474
services/mux/mux_client.go
Normal file
@ -0,0 +1,474 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
logger "log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
clienttransport "github.com/snail007/goproxy/core/cs/client"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
"github.com/snail007/goproxy/services"
|
||||
"github.com/snail007/goproxy/utils"
|
||||
"github.com/snail007/goproxy/utils/jumper"
|
||||
"github.com/snail007/goproxy/utils/mapx"
|
||||
//"github.com/xtaci/smux"
|
||||
smux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
type MuxClientArgs struct {
|
||||
Parent *string
|
||||
ParentType *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Key *string
|
||||
Timeout *int
|
||||
IsCompress *bool
|
||||
SessionCount *int
|
||||
KCP kcpcfg.KCPConfigArgs
|
||||
Jumper *string
|
||||
TCPSMethod *string
|
||||
TCPSPassword *string
|
||||
TOUMethod *string
|
||||
TOUPassword *string
|
||||
}
|
||||
type ClientUDPConnItem struct {
|
||||
conn *smux.Stream
|
||||
isActive bool
|
||||
touchtime int64
|
||||
srcAddr *net.UDPAddr
|
||||
localAddr *net.UDPAddr
|
||||
udpConn *net.UDPConn
|
||||
connid string
|
||||
}
|
||||
type MuxClient struct {
|
||||
cfg MuxClientArgs
|
||||
isStop bool
|
||||
sessions mapx.ConcurrentMap
|
||||
log *logger.Logger
|
||||
jumper *jumper.Jumper
|
||||
udpConns mapx.ConcurrentMap
|
||||
}
|
||||
|
||||
func NewMuxClient() services.Service {
|
||||
return &MuxClient{
|
||||
cfg: MuxClientArgs{},
|
||||
isStop: false,
|
||||
sessions: mapx.NewConcurrentMap(),
|
||||
udpConns: mapx.NewConcurrentMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MuxClient) InitService() (err error) {
|
||||
s.UDPGCDeamon()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *MuxClient) CheckArgs() (err error) {
|
||||
if *s.cfg.Parent != "" {
|
||||
s.log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||
} else {
|
||||
err = fmt.Errorf("parent required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||
err = fmt.Errorf("cert and key file required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if *s.cfg.Jumper != "" {
|
||||
if *s.cfg.ParentType != "tls" && *s.cfg.ParentType != "tcp" {
|
||||
err = fmt.Errorf("jumper only worked of -T is tls or tcp")
|
||||
return
|
||||
}
|
||||
var j jumper.Jumper
|
||||
j, err = jumper.New(*s.cfg.Jumper, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse jumper fail, err %s", err)
|
||||
return
|
||||
}
|
||||
s.jumper = &j
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxClient) StopService() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stop client service crashed,%s", e)
|
||||
} else {
|
||||
s.log.Printf("service client stopped")
|
||||
}
|
||||
s.cfg = MuxClientArgs{}
|
||||
s.jumper = nil
|
||||
s.log = nil
|
||||
s.sessions = nil
|
||||
s.udpConns = nil
|
||||
s = nil
|
||||
}()
|
||||
s.isStop = true
|
||||
for _, sess := range s.sessions.Items() {
|
||||
sess.(*smux.Session).Close()
|
||||
}
|
||||
}
|
||||
func (s *MuxClient) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(MuxClientArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
s.log.Printf("client started")
|
||||
count := 1
|
||||
if *s.cfg.SessionCount > 0 {
|
||||
count = *s.cfg.SessionCount
|
||||
}
|
||||
for i := 1; i <= count; i++ {
|
||||
key := fmt.Sprintf("worker[%d]", i)
|
||||
s.log.Printf("session %s started", key)
|
||||
go func(i int) {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("session worker crashed: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
conn, err := s.getParentConn()
|
||||
if err != nil {
|
||||
s.log.Printf("connection err: %s, retrying...", err)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
g := sync.WaitGroup{}
|
||||
g.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
g.Done()
|
||||
}()
|
||||
_, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i)))
|
||||
}()
|
||||
g.Wait()
|
||||
conn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
s.log.Printf("connection err: %s, retrying...", err)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
session, err := smux.Server(conn, nil)
|
||||
if err != nil {
|
||||
s.log.Printf("session err: %s, retrying...", err)
|
||||
conn.Close()
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
if _sess, ok := s.sessions.Get(key); ok {
|
||||
_sess.(*smux.Session).Close()
|
||||
}
|
||||
s.sessions.Set(key, session)
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
s.log.Printf("accept stream err: %s, retrying...", err)
|
||||
session.Close()
|
||||
time.Sleep(time.Second * 3)
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stream handler crashed: %s", e)
|
||||
}
|
||||
}()
|
||||
var ID, clientLocalAddr, serverID string
|
||||
stream.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID)
|
||||
stream.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("read stream signal err: %s", err)
|
||||
stream.Close()
|
||||
return
|
||||
}
|
||||
//s.log.Printf("worker[%d] signal revecived,server %s stream %s %s", i, serverID, ID, clientLocalAddr)
|
||||
protocol := clientLocalAddr[:3]
|
||||
localAddr := clientLocalAddr[4:]
|
||||
if protocol == "udp" {
|
||||
s.ServeUDP(stream, localAddr, ID)
|
||||
} else {
|
||||
s.ServeConn(stream, localAddr, ID)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxClient) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *MuxClient) getParentConn() (conn net.Conn, err error) {
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
if s.jumper == nil {
|
||||
var _conn tls.Conn
|
||||
_conn, err = clienttransport.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||
if err == nil {
|
||||
conn = net.Conn(&_conn)
|
||||
}
|
||||
} else {
|
||||
conf, e := utils.TlsConfig(s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
var _c net.Conn
|
||||
_c, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err == nil {
|
||||
conn = net.Conn(tls.Client(_c, conf))
|
||||
}
|
||||
}
|
||||
|
||||
} else if *s.cfg.ParentType == "kcp" {
|
||||
conn, err = clienttransport.KCPConnectHost(*s.cfg.Parent, s.cfg.KCP)
|
||||
} else if *s.cfg.ParentType == "tcps" {
|
||||
if s.jumper == nil {
|
||||
conn, err = clienttransport.TCPSConnectHost(*s.cfg.Parent, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword, false, *s.cfg.Timeout)
|
||||
} else {
|
||||
conn, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err == nil {
|
||||
conn, err = encryptconn.NewConn(conn, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword)
|
||||
}
|
||||
}
|
||||
|
||||
} else if *s.cfg.ParentType == "tou" {
|
||||
conn, err = clienttransport.TOUConnectHost(*s.cfg.Parent, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword, false, *s.cfg.Timeout)
|
||||
} else {
|
||||
if s.jumper == nil {
|
||||
conn, err = clienttransport.TCPConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||
} else {
|
||||
conn, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) {
|
||||
var item *ClientUDPConnItem
|
||||
var body []byte
|
||||
var err error
|
||||
srcAddr := ""
|
||||
defer func() {
|
||||
if item != nil {
|
||||
(*item).conn.Close()
|
||||
(*item).udpConn.Close()
|
||||
s.udpConns.Remove(srcAddr)
|
||||
inConn.Close()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
srcAddr, body, err = utils.ReadUDPPacket(inConn)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "n != int(") {
|
||||
continue
|
||||
}
|
||||
if !utils.IsNetDeadlineErr(err) && err != io.EOF {
|
||||
s.log.Printf("udp packet revecived from bridge fail, err: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if v, ok := s.udpConns.Get(srcAddr); !ok {
|
||||
_srcAddr, _ := net.ResolveUDPAddr("udp", srcAddr)
|
||||
zeroAddr, _ := net.ResolveUDPAddr("udp", ":")
|
||||
_localAddr, _ := net.ResolveUDPAddr("udp", localAddr)
|
||||
c, err := net.DialUDP("udp", zeroAddr, _localAddr)
|
||||
if err != nil {
|
||||
s.log.Printf("create local udp conn fail, err : %s", err)
|
||||
inConn.Close()
|
||||
return
|
||||
}
|
||||
item = &ClientUDPConnItem{
|
||||
conn: inConn,
|
||||
srcAddr: _srcAddr,
|
||||
localAddr: _localAddr,
|
||||
udpConn: c,
|
||||
connid: ID,
|
||||
}
|
||||
s.udpConns.Set(srcAddr, item)
|
||||
s.UDPRevecive(srcAddr, ID)
|
||||
} else {
|
||||
item = v.(*ClientUDPConnItem)
|
||||
}
|
||||
(*item).touchtime = time.Now().Unix()
|
||||
go func() {
|
||||
defer func() { _ = recover() }()
|
||||
(*item).udpConn.Write(body)
|
||||
}()
|
||||
}
|
||||
}
|
||||
func (s *MuxClient) UDPRevecive(key, ID string) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
s.log.Printf("udp conn %s connected", ID)
|
||||
v, ok := s.udpConns.Get(key)
|
||||
if !ok {
|
||||
s.log.Printf("[warn] udp conn not exists for %s, connid : %s", key, ID)
|
||||
return
|
||||
}
|
||||
cui := v.(*ClientUDPConnItem)
|
||||
buf := utils.LeakyBuffer.Get()
|
||||
defer func() {
|
||||
utils.LeakyBuffer.Put(buf)
|
||||
cui.conn.Close()
|
||||
cui.udpConn.Close()
|
||||
s.udpConns.Remove(key)
|
||||
s.log.Printf("udp conn %s released", ID)
|
||||
}()
|
||||
for {
|
||||
n, err := cui.udpConn.Read(buf)
|
||||
if err != nil {
|
||||
if !utils.IsNetClosedErr(err) {
|
||||
s.log.Printf("udp conn read udp packet fail , err: %s ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
cui.touchtime = time.Now().Unix()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
cui.conn.SetWriteDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
_, err = cui.conn.Write(utils.UDPPacket(cui.srcAddr.String(), buf[:n]))
|
||||
cui.conn.SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
cui.udpConn.Close()
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (s *MuxClient) UDPGCDeamon() {
|
||||
gctime := int64(30)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
timer := time.NewTicker(time.Second)
|
||||
for {
|
||||
<-timer.C
|
||||
gcKeys := []string{}
|
||||
s.udpConns.IterCb(func(key string, v interface{}) {
|
||||
if time.Now().Unix()-v.(*ClientUDPConnItem).touchtime > gctime {
|
||||
(*(v.(*ClientUDPConnItem).conn)).Close()
|
||||
(v.(*ClientUDPConnItem).udpConn).Close()
|
||||
gcKeys = append(gcKeys, key)
|
||||
s.log.Printf("gc udp conn %s", v.(*ClientUDPConnItem).connid)
|
||||
}
|
||||
})
|
||||
for _, k := range gcKeys {
|
||||
s.udpConns.Remove(k)
|
||||
}
|
||||
gcKeys = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (s *MuxClient) ServeConn(inConn *smux.Stream, localAddr, ID string) {
|
||||
var err error
|
||||
var outConn net.Conn
|
||||
i := 0
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
i++
|
||||
outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout)
|
||||
if err == nil || i == 3 {
|
||||
break
|
||||
} else {
|
||||
if i == 3 {
|
||||
s.log.Printf("connect to %s err: %s, retrying...", localAddr, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
inConn.Close()
|
||||
utils.CloseConn(&outConn)
|
||||
s.log.Printf("build connection error, err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.log.Printf("stream %s created", ID)
|
||||
if *s.cfg.IsCompress {
|
||||
die1 := make(chan bool, 1)
|
||||
die2 := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(outConn, snappy.NewReader(inConn))
|
||||
die1 <- true
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(snappy.NewWriter(inConn), outConn)
|
||||
die2 <- true
|
||||
}()
|
||||
select {
|
||||
case <-die1:
|
||||
case <-die2:
|
||||
}
|
||||
outConn.Close()
|
||||
inConn.Close()
|
||||
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||
} else {
|
||||
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||
s.log.Printf("stream %s released", ID)
|
||||
}, s.log)
|
||||
}
|
||||
}
|
||||
602
services/mux/mux_server.go
Normal file
@ -0,0 +1,602 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
logger "log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clienttransport "github.com/snail007/goproxy/core/cs/client"
|
||||
server "github.com/snail007/goproxy/core/cs/server"
|
||||
"github.com/snail007/goproxy/core/lib/kcpcfg"
|
||||
encryptconn "github.com/snail007/goproxy/core/lib/transport/encrypt"
|
||||
"github.com/snail007/goproxy/services"
|
||||
"github.com/snail007/goproxy/utils"
|
||||
"github.com/snail007/goproxy/utils/jumper"
|
||||
"github.com/snail007/goproxy/utils/mapx"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
//"github.com/xtaci/smux"
|
||||
smux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
const (
|
||||
CONN_CLIENT_CONTROL = uint8(1)
|
||||
CONN_SERVER = uint8(4)
|
||||
CONN_CLIENT = uint8(5)
|
||||
)
|
||||
|
||||
type MuxServerArgs struct {
|
||||
Parent *string
|
||||
ParentType *string
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CertBytes []byte
|
||||
KeyBytes []byte
|
||||
Local *string
|
||||
IsUDP *bool
|
||||
Key *string
|
||||
Remote *string
|
||||
Timeout *int
|
||||
Route *[]string
|
||||
Mgr *MuxServerManager
|
||||
IsCompress *bool
|
||||
SessionCount *int
|
||||
KCP kcpcfg.KCPConfigArgs
|
||||
Jumper *string
|
||||
TCPSMethod *string
|
||||
TCPSPassword *string
|
||||
TOUMethod *string
|
||||
TOUPassword *string
|
||||
}
|
||||
type MuxServer struct {
|
||||
cfg MuxServerArgs
|
||||
sc server.ServerChannel
|
||||
sessions mapx.ConcurrentMap
|
||||
lockChn chan bool
|
||||
isStop bool
|
||||
log *logger.Logger
|
||||
jumper *jumper.Jumper
|
||||
udpConns mapx.ConcurrentMap
|
||||
}
|
||||
|
||||
type MuxServerManager struct {
|
||||
cfg MuxServerArgs
|
||||
serverID string
|
||||
servers []*services.Service
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func NewMuxServerManager() services.Service {
|
||||
return &MuxServerManager{
|
||||
cfg: MuxServerArgs{},
|
||||
serverID: utils.Uniqueid(),
|
||||
servers: []*services.Service{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MuxServerManager) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(MuxServerArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if *s.cfg.Parent != "" {
|
||||
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||
} else {
|
||||
err = fmt.Errorf("parent required")
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.log.Printf("server id: %s", s.serverID)
|
||||
//s.log.Printf("route:%v", *s.cfg.Route)
|
||||
for _, _info := range *s.cfg.Route {
|
||||
if _info == "" {
|
||||
continue
|
||||
}
|
||||
IsUDP := *s.cfg.IsUDP
|
||||
if strings.HasPrefix(_info, "udp://") {
|
||||
IsUDP = true
|
||||
}
|
||||
info := strings.TrimPrefix(_info, "udp://")
|
||||
info = strings.TrimPrefix(info, "tcp://")
|
||||
_routeInfo := strings.Split(info, "@")
|
||||
server := NewMuxServer()
|
||||
|
||||
local := _routeInfo[0]
|
||||
remote := _routeInfo[1]
|
||||
KEY := *s.cfg.Key
|
||||
if strings.HasPrefix(remote, "[") {
|
||||
KEY = remote[1:strings.LastIndex(remote, "]")]
|
||||
remote = remote[strings.LastIndex(remote, "]")+1:]
|
||||
}
|
||||
if strings.HasPrefix(remote, ":") {
|
||||
remote = fmt.Sprintf("127.0.0.1%s", remote)
|
||||
}
|
||||
err = server.Start(MuxServerArgs{
|
||||
CertBytes: s.cfg.CertBytes,
|
||||
KeyBytes: s.cfg.KeyBytes,
|
||||
Parent: s.cfg.Parent,
|
||||
CertFile: s.cfg.CertFile,
|
||||
KeyFile: s.cfg.KeyFile,
|
||||
Local: &local,
|
||||
IsUDP: &IsUDP,
|
||||
Remote: &remote,
|
||||
Key: &KEY,
|
||||
Timeout: s.cfg.Timeout,
|
||||
Mgr: s,
|
||||
IsCompress: s.cfg.IsCompress,
|
||||
SessionCount: s.cfg.SessionCount,
|
||||
KCP: s.cfg.KCP,
|
||||
ParentType: s.cfg.ParentType,
|
||||
Jumper: s.cfg.Jumper,
|
||||
TCPSMethod: s.cfg.TCPSMethod,
|
||||
TCPSPassword: s.cfg.TCPSPassword,
|
||||
TOUMethod: s.cfg.TOUMethod,
|
||||
TOUPassword: s.cfg.TOUPassword,
|
||||
}, log)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.servers = append(s.servers, &server)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServerManager) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *MuxServerManager) StopService() {
|
||||
for _, server := range s.servers {
|
||||
(*server).Clean()
|
||||
}
|
||||
s.cfg = MuxServerArgs{}
|
||||
s.log = nil
|
||||
s.serverID = ""
|
||||
s.servers = nil
|
||||
s = nil
|
||||
}
|
||||
func (s *MuxServerManager) CheckArgs() (err error) {
|
||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||
err = fmt.Errorf("cert and key file required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServerManager) InitService() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func NewMuxServer() services.Service {
|
||||
return &MuxServer{
|
||||
cfg: MuxServerArgs{},
|
||||
lockChn: make(chan bool, 1),
|
||||
sessions: mapx.NewConcurrentMap(),
|
||||
isStop: false,
|
||||
udpConns: mapx.NewConcurrentMap(),
|
||||
}
|
||||
}
|
||||
|
||||
type MuxUDPConnItem struct {
|
||||
conn *net.Conn
|
||||
touchtime int64
|
||||
srcAddr *net.UDPAddr
|
||||
localAddr *net.UDPAddr
|
||||
connid string
|
||||
}
|
||||
|
||||
func (s *MuxServer) StopService() {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
s.log.Printf("stop server service crashed,%s", e)
|
||||
} else {
|
||||
s.log.Printf("service server stopped")
|
||||
}
|
||||
s.cfg = MuxServerArgs{}
|
||||
s.jumper = nil
|
||||
s.lockChn = nil
|
||||
s.log = nil
|
||||
s.sc = server.ServerChannel{}
|
||||
s.sessions = nil
|
||||
s.udpConns = nil
|
||||
s = nil
|
||||
}()
|
||||
s.isStop = true
|
||||
for _, sess := range s.sessions.Items() {
|
||||
sess.(*smux.Session).Close()
|
||||
}
|
||||
if s.sc.Listener != nil {
|
||||
(*s.sc.Listener).Close()
|
||||
}
|
||||
if s.sc.UDPListener != nil {
|
||||
(*s.sc.UDPListener).Close()
|
||||
}
|
||||
}
|
||||
func (s *MuxServer) InitService() (err error) {
|
||||
s.UDPGCDeamon()
|
||||
return
|
||||
}
|
||||
func (s *MuxServer) CheckArgs() (err error) {
|
||||
if *s.cfg.Remote == "" {
|
||||
err = fmt.Errorf("remote required")
|
||||
return
|
||||
}
|
||||
if *s.cfg.Jumper != "" {
|
||||
if *s.cfg.ParentType != "tls" && *s.cfg.ParentType != "tcp" {
|
||||
err = fmt.Errorf("jumper only worked of -T is tls or tcp")
|
||||
return
|
||||
}
|
||||
var j jumper.Jumper
|
||||
j, err = jumper.New(*s.cfg.Jumper, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("parse jumper fail, err %s", err)
|
||||
return
|
||||
}
|
||||
s.jumper = &j
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *MuxServer) Start(args interface{}, log *logger.Logger) (err error) {
|
||||
s.log = log
|
||||
s.cfg = args.(MuxServerArgs)
|
||||
if err = s.CheckArgs(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.InitService(); err != nil {
|
||||
return
|
||||
}
|
||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||
p, _ := strconv.Atoi(port)
|
||||
s.sc = server.NewServerChannel(host, p, s.log)
|
||||
if *s.cfg.IsUDP {
|
||||
err = s.sc.ListenUDP(func(listener *net.UDPConn, packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||
s.UDPSend(packet, localAddr, srcAddr)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.log.Printf("server on %s", (*s.sc.UDPListener).LocalAddr())
|
||||
} else {
|
||||
err = s.sc.ListenTCP(func(inConn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
s.log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
var outConn net.Conn
|
||||
var ID string
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
outConn, ID, err = s.GetOutConn()
|
||||
if err != nil {
|
||||
utils.CloseConn(&outConn)
|
||||
s.log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.log.Printf("%s stream %s created", *s.cfg.Key, ID)
|
||||
if *s.cfg.IsCompress {
|
||||
die1 := make(chan bool, 1)
|
||||
die2 := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(inConn, snappy.NewReader(outConn))
|
||||
die1 <- true
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
io.Copy(snappy.NewWriter(outConn), inConn)
|
||||
die2 <- true
|
||||
}()
|
||||
select {
|
||||
case <-die1:
|
||||
case <-die2:
|
||||
}
|
||||
outConn.Close()
|
||||
inConn.Close()
|
||||
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||
} else {
|
||||
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||
}, s.log)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.log.Printf("server on %s", (*s.sc.Listener).Addr())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServer) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) {
|
||||
i := 1
|
||||
if *s.cfg.SessionCount > 0 {
|
||||
i = rand.Intn(*s.cfg.SessionCount)
|
||||
}
|
||||
outConn, err = s.GetConn(fmt.Sprintf("%d", i))
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "can not connect at same time") {
|
||||
s.log.Printf("connection err: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
remoteAddr := "tcp:" + *s.cfg.Remote
|
||||
if *s.cfg.IsUDP {
|
||||
remoteAddr = "udp:" + *s.cfg.Remote
|
||||
}
|
||||
ID = utils.Uniqueid()
|
||||
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
_, err = outConn.Write(utils.BuildPacketData(ID, remoteAddr, s.cfg.Mgr.serverID))
|
||||
outConn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("write stream data err: %s ,retrying...", err)
|
||||
utils.CloseConn(&outConn)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) {
|
||||
select {
|
||||
case s.lockChn <- true:
|
||||
default:
|
||||
err = fmt.Errorf("can not connect at same time")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
<-s.lockChn
|
||||
}()
|
||||
var session *smux.Session
|
||||
_session, ok := s.sessions.Get(index)
|
||||
if !ok {
|
||||
var c net.Conn
|
||||
c, err = s.getParentConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
_, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID))
|
||||
c.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
session, err = smux.Client(c, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if _sess, ok := s.sessions.Get(index); ok {
|
||||
_sess.(*smux.Session).Close()
|
||||
}
|
||||
s.sessions.Set(index, session)
|
||||
s.log.Printf("session[%s] created", index)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
if session.IsClosed() {
|
||||
s.sessions.Remove(index)
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
session = _session.(*smux.Session)
|
||||
}
|
||||
conn, err = session.OpenStream()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
s.sessions.Remove(index)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServer) getParentConn() (conn net.Conn, err error) {
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
if s.jumper == nil {
|
||||
var _conn tls.Conn
|
||||
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||
if err == nil {
|
||||
conn = net.Conn(&_conn)
|
||||
}
|
||||
} else {
|
||||
conf, err := utils.TlsConfig(s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var _c net.Conn
|
||||
_c, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err == nil {
|
||||
conn = net.Conn(tls.Client(_c, conf))
|
||||
}
|
||||
}
|
||||
|
||||
} else if *s.cfg.ParentType == "kcp" {
|
||||
conn, err = utils.ConnectKCPHost(*s.cfg.Parent, s.cfg.KCP)
|
||||
} else if *s.cfg.ParentType == "tcps" {
|
||||
if s.jumper == nil {
|
||||
conn, err = clienttransport.TCPSConnectHost(*s.cfg.Parent, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword, false, *s.cfg.Timeout)
|
||||
} else {
|
||||
conn, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
if err == nil {
|
||||
conn, err = encryptconn.NewConn(conn, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword)
|
||||
}
|
||||
}
|
||||
|
||||
} else if *s.cfg.ParentType == "tou" {
|
||||
conn, err = clienttransport.TOUConnectHost(*s.cfg.Parent, *s.cfg.TCPSMethod, *s.cfg.TCPSPassword, false, *s.cfg.Timeout)
|
||||
} else {
|
||||
if s.jumper == nil {
|
||||
conn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||
} else {
|
||||
conn, err = s.jumper.Dial(*s.cfg.Parent, time.Millisecond*time.Duration(*s.cfg.Timeout))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MuxServer) UDPGCDeamon() {
|
||||
gctime := int64(30)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if s.isStop {
|
||||
return
|
||||
}
|
||||
timer := time.NewTicker(time.Second)
|
||||
for {
|
||||
<-timer.C
|
||||
gcKeys := []string{}
|
||||
s.udpConns.IterCb(func(key string, v interface{}) {
|
||||
if time.Now().Unix()-v.(*MuxUDPConnItem).touchtime > gctime {
|
||||
(*(v.(*MuxUDPConnItem).conn)).Close()
|
||||
gcKeys = append(gcKeys, key)
|
||||
s.log.Printf("gc udp conn %s", v.(*MuxUDPConnItem).connid)
|
||||
}
|
||||
})
|
||||
for _, k := range gcKeys {
|
||||
s.udpConns.Remove(k)
|
||||
}
|
||||
gcKeys = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (s *MuxServer) UDPSend(data []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||
var (
|
||||
uc *MuxUDPConnItem
|
||||
key = srcAddr.String()
|
||||
ID string
|
||||
err error
|
||||
outconn net.Conn
|
||||
)
|
||||
v, ok := s.udpConns.Get(key)
|
||||
if !ok {
|
||||
for {
|
||||
outconn, ID, err = s.GetOutConn()
|
||||
if err != nil && strings.Contains(err.Error(), "can not connect at same time") {
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
s.log.Printf("connect to %s fail, err: %s", *s.cfg.Parent, err)
|
||||
return
|
||||
}
|
||||
uc = &MuxUDPConnItem{
|
||||
conn: &outconn,
|
||||
srcAddr: srcAddr,
|
||||
localAddr: localAddr,
|
||||
connid: ID,
|
||||
}
|
||||
s.udpConns.Set(key, uc)
|
||||
s.UDPRevecive(key, ID)
|
||||
} else {
|
||||
uc = v.(*MuxUDPConnItem)
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
(*uc.conn).Close()
|
||||
s.udpConns.Remove(key)
|
||||
s.log.Printf("udp sender crashed with error : %s", e)
|
||||
}
|
||||
}()
|
||||
uc.touchtime = time.Now().Unix()
|
||||
(*uc.conn).SetWriteDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||
_, err = (*uc.conn).Write(utils.UDPPacket(srcAddr.String(), data))
|
||||
(*uc.conn).SetWriteDeadline(time.Time{})
|
||||
if err != nil {
|
||||
s.log.Printf("write udp packet to %s fail ,flush err:%s ", *s.cfg.Parent, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (s *MuxServer) UDPRevecive(key, ID string) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
s.log.Printf("udp conn %s connected", ID)
|
||||
var uc *MuxUDPConnItem
|
||||
defer func() {
|
||||
if uc != nil {
|
||||
(*uc.conn).Close()
|
||||
}
|
||||
s.udpConns.Remove(key)
|
||||
s.log.Printf("udp conn %s released", ID)
|
||||
}()
|
||||
v, ok := s.udpConns.Get(key)
|
||||
if !ok {
|
||||
s.log.Printf("[warn] udp conn not exists for %s, connid : %s", key, ID)
|
||||
return
|
||||
}
|
||||
uc = v.(*MuxUDPConnItem)
|
||||
for {
|
||||
_, body, err := utils.ReadUDPPacket(*uc.conn)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "n != int(") {
|
||||
continue
|
||||
}
|
||||
if err != io.EOF {
|
||||
s.log.Printf("udp conn read udp packet fail , err: %s ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
uc.touchtime = time.Now().Unix()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
fmt.Printf("crashed, err: %s\nstack:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
s.sc.UDPListener.WriteToUDP(body, uc.srcAddr)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -2,50 +2,68 @@ package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
logger "log"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Start(args interface{}) (err error)
|
||||
Start(args interface{}, log *logger.Logger) (err error)
|
||||
Clean()
|
||||
}
|
||||
type ServiceItem struct {
|
||||
S Service
|
||||
Args interface{}
|
||||
Name string
|
||||
Log *logger.Logger
|
||||
}
|
||||
|
||||
var servicesMap = map[string]*ServiceItem{}
|
||||
var servicesMap = sync.Map{}
|
||||
|
||||
func Regist(name string, s Service, args interface{}) {
|
||||
servicesMap[name] = &ServiceItem{
|
||||
func Regist(name string, s Service, args interface{}, log *logger.Logger) {
|
||||
Stop(name)
|
||||
servicesMap.Store(name, &ServiceItem{
|
||||
S: s,
|
||||
Args: args,
|
||||
Name: name,
|
||||
Log: log,
|
||||
})
|
||||
}
|
||||
func GetService(name string) *ServiceItem {
|
||||
if s, ok := servicesMap.Load(name); ok && s.(*ServiceItem).S != nil {
|
||||
return s.(*ServiceItem)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
func Stop(name string) {
|
||||
if s, ok := servicesMap.Load(name); ok && s.(*ServiceItem).S != nil {
|
||||
s.(*ServiceItem).S.Clean()
|
||||
*s.(*ServiceItem) = ServiceItem{}
|
||||
s = nil
|
||||
servicesMap.Store(name, nil)
|
||||
servicesMap.Delete(name)
|
||||
}
|
||||
}
|
||||
func Run(name string, args ...interface{}) (service *ServiceItem, err error) {
|
||||
service, ok := servicesMap[name]
|
||||
func Run(name string, args interface{}) (service *ServiceItem, err error) {
|
||||
_service, ok := servicesMap.Load(name)
|
||||
if ok {
|
||||
go func() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.Fatalf("%s servcie crashed, ERR: %s\ntrace:%s", name, err, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
if len(args) == 1 {
|
||||
err = service.S.Start(args[0])
|
||||
} else {
|
||||
err = service.S.Start(service.Args)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("%s servcie fail, ERR: %s", name, err)
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
err = fmt.Errorf("%s servcie crashed, ERR: %s\ntrace:%s", name, e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if !ok {
|
||||
service = _service.(*ServiceItem)
|
||||
if args != nil {
|
||||
err = service.S.Start(args, service.Log)
|
||||
} else {
|
||||
err = service.S.Start(service.Args, service.Log)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s servcie fail, ERR: %s", name, err)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("service %s not found", name)
|
||||
}
|
||||
return
|
||||
|
||||
@ -1,621 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"proxy/utils"
|
||||
"proxy/utils/aes"
|
||||
"proxy/utils/socks"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Socks struct {
|
||||
cfg SocksArgs
|
||||
checker utils.Checker
|
||||
basicAuth utils.BasicAuth
|
||||
sshClient *ssh.Client
|
||||
lockChn chan bool
|
||||
udpSC utils.ServerChannel
|
||||
}
|
||||
|
||||
func NewSocks() Service {
|
||||
return &Socks{
|
||||
cfg: SocksArgs{},
|
||||
checker: utils.Checker{},
|
||||
basicAuth: utils.BasicAuth{},
|
||||
lockChn: make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Socks) CheckArgs() {
|
||||
var err error
|
||||
if *s.cfg.LocalType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
}
|
||||
if *s.cfg.Parent != "" {
|
||||
if *s.cfg.ParentType == "" {
|
||||
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
||||
}
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
if *s.cfg.SSHUser == "" {
|
||||
log.Fatalf("ssh user required")
|
||||
}
|
||||
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||
log.Fatalf("ssh password or key required")
|
||||
}
|
||||
if *s.cfg.SSHPassword != "" {
|
||||
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||
} else {
|
||||
var SSHSigner ssh.Signer
|
||||
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("read key file ERR: %s", err)
|
||||
}
|
||||
if *s.cfg.SSHKeyFileSalt != "" {
|
||||
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||
} else {
|
||||
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
||||
}
|
||||
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (s *Socks) InitService() {
|
||||
s.InitBasicAuth()
|
||||
s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
err := s.ConnectSSH()
|
||||
if err != nil {
|
||||
log.Fatalf("init service fail, ERR: %s", err)
|
||||
}
|
||||
go func() {
|
||||
//循环检查ssh网络连通性
|
||||
for {
|
||||
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
||||
if err == nil {
|
||||
_, err = conn.Write([]byte{0})
|
||||
}
|
||||
if err != nil {
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
log.Printf("ssh offline, retrying...")
|
||||
s.ConnectSSH()
|
||||
} else {
|
||||
conn.Close()
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
log.Println("warn: socks udp not suppored for ssh")
|
||||
} else {
|
||||
|
||||
s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal)
|
||||
err := s.udpSC.ListenUDP(s.udpCallback)
|
||||
if err != nil {
|
||||
log.Fatalf("init udp service fail, ERR: %s", err)
|
||||
}
|
||||
log.Printf("udp socks proxy on %s", s.udpSC.UDPListener.LocalAddr())
|
||||
}
|
||||
}
|
||||
func (s *Socks) StopService() {
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
if s.udpSC.UDPListener != nil {
|
||||
s.udpSC.UDPListener.Close()
|
||||
}
|
||||
}
|
||||
func (s *Socks) Start(args interface{}) (err error) {
|
||||
//start()
|
||||
s.cfg = args.(SocksArgs)
|
||||
s.CheckArgs()
|
||||
s.InitService()
|
||||
if *s.cfg.Parent != "" {
|
||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||
}
|
||||
sc := utils.NewServerChannelHost(*s.cfg.Local)
|
||||
if *s.cfg.LocalType == TYPE_TCP {
|
||||
err = sc.ListenTCP(s.socksConnCallback)
|
||||
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.socksConnCallback)
|
||||
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.socksConnCallback)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s socks proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||
return
|
||||
}
|
||||
func (s *Socks) Clean() {
|
||||
s.StopService()
|
||||
}
|
||||
func (s *Socks) UDPKey() []byte {
|
||||
return s.cfg.KeyBytes[:32]
|
||||
}
|
||||
func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||
rawB := b
|
||||
var err error
|
||||
if *s.cfg.LocalType == "tls" {
|
||||
//decode b
|
||||
rawB, err = goaes.Decrypt(s.UDPKey(), b)
|
||||
if err != nil {
|
||||
log.Printf("decrypt udp packet fail from %s", srcAddr.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
p, err := socks.ParseUDPPacket(rawB)
|
||||
log.Printf("udp revecived:%v", len(p.Data()))
|
||||
if err != nil {
|
||||
log.Printf("parse udp packet fail, ERR:%s", err)
|
||||
return
|
||||
}
|
||||
//防止死循环
|
||||
if s.IsDeadLoop((*localAddr).String(), p.Host()) {
|
||||
log.Printf("dead loop detected , %s", p.Host())
|
||||
return
|
||||
}
|
||||
//log.Printf("##########udp to -> %s:%s###########", p.Host(), p.Port())
|
||||
if *s.cfg.Parent != "" {
|
||||
//有上级代理,转发给上级
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
//encode b
|
||||
rawB, err = goaes.Encrypt(s.UDPKey(), rawB)
|
||||
if err != nil {
|
||||
log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
||||
return
|
||||
}
|
||||
}
|
||||
parent := *s.cfg.UDPParent
|
||||
if parent == "" {
|
||||
parent = *s.cfg.Parent
|
||||
}
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", parent)
|
||||
if err != nil {
|
||||
log.Printf("can't resolve address: %s", err)
|
||||
return
|
||||
}
|
||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||
if err != nil {
|
||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||
return
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5)))
|
||||
_, err = conn.Write(rawB)
|
||||
log.Printf("udp request:%v", len(rawB))
|
||||
if err != nil {
|
||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
||||
buf := make([]byte, 10*1024)
|
||||
length, _, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
respBody := buf[0:length]
|
||||
log.Printf("udp response:%v", len(respBody))
|
||||
//log.Printf("revecived udp packet from %s", dstAddr.String())
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
//decode b
|
||||
respBody, err = goaes.Decrypt(s.UDPKey(), respBody)
|
||||
if err != nil {
|
||||
log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
if *s.cfg.LocalType == "tls" {
|
||||
d, err := goaes.Encrypt(s.UDPKey(), respBody)
|
||||
if err != nil {
|
||||
log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||
log.Printf("udp reply:%v", len(d))
|
||||
} else {
|
||||
s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr)
|
||||
log.Printf("udp reply:%v", len(respBody))
|
||||
}
|
||||
|
||||
} else {
|
||||
//本地代理
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(p.Host(), p.Port()))
|
||||
if err != nil {
|
||||
log.Printf("can't resolve address: %s", err)
|
||||
return
|
||||
}
|
||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||
if err != nil {
|
||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||
return
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3)))
|
||||
_, err = conn.Write(p.Data())
|
||||
log.Printf("udp send:%v", len(p.Data()))
|
||||
if err != nil {
|
||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
||||
buf := make([]byte, 10*1024)
|
||||
length, _, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
respBody := buf[0:length]
|
||||
//封装来自真实服务器的数据,返回给访问者
|
||||
respPacket := p.NewReply(respBody)
|
||||
//log.Printf("revecived udp packet from %s", dstAddr.String())
|
||||
if *s.cfg.LocalType == "tls" {
|
||||
d, err := goaes.Encrypt(s.UDPKey(), respPacket)
|
||||
if err != nil {
|
||||
log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||
} else {
|
||||
s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr)
|
||||
}
|
||||
log.Printf("udp reply:%v", len(respPacket))
|
||||
}
|
||||
|
||||
}
|
||||
func (s *Socks) socksConnCallback(inConn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("socks conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||
inConn.Close()
|
||||
}
|
||||
}()
|
||||
//协商开始
|
||||
|
||||
//method select request
|
||||
inConn.SetReadDeadline(time.Now().Add(time.Second * 3))
|
||||
methodReq, err := socks.NewMethodsRequest(inConn)
|
||||
inConn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||
utils.CloseConn(&inConn)
|
||||
log.Printf("new methods request fail,ERR: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.IsBasicAuth() {
|
||||
if !methodReq.Select(socks.Method_NO_AUTH) {
|
||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||
utils.CloseConn(&inConn)
|
||||
log.Printf("none method found : Method_NO_AUTH")
|
||||
return
|
||||
}
|
||||
//method select reply
|
||||
err = methodReq.Reply(socks.Method_NO_AUTH)
|
||||
if err != nil {
|
||||
log.Printf("reply answer data fail,ERR: %s", err)
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
// log.Printf("% x", methodReq.Bytes())
|
||||
} else {
|
||||
//auth
|
||||
if !methodReq.Select(socks.Method_USER_PASS) {
|
||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||
utils.CloseConn(&inConn)
|
||||
log.Printf("none method found : Method_USER_PASS")
|
||||
return
|
||||
}
|
||||
//method reply need auth
|
||||
err = methodReq.Reply(socks.Method_USER_PASS)
|
||||
if err != nil {
|
||||
log.Printf("reply answer data fail,ERR: %s", err)
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
//read auth
|
||||
buf := make([]byte, 500)
|
||||
inConn.SetReadDeadline(time.Now().Add(time.Second * 3))
|
||||
n, err := inConn.Read(buf)
|
||||
inConn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
r := buf[:n]
|
||||
user := string(r[2 : r[1]+2])
|
||||
pass := string(r[2+r[1]+1:])
|
||||
//log.Printf("user:%s,pass:%s", user, pass)
|
||||
//auth
|
||||
_addr := strings.Split(inConn.RemoteAddr().String(), ":")
|
||||
if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") {
|
||||
inConn.Write([]byte{0x01, 0x00})
|
||||
} else {
|
||||
inConn.Write([]byte{0x01, 0x01})
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//request detail
|
||||
request, err := socks.NewRequest(inConn)
|
||||
if err != nil {
|
||||
log.Printf("read request data fail,ERR: %s", err)
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
}
|
||||
//协商结束
|
||||
|
||||
switch request.CMD() {
|
||||
case socks.CMD_BIND:
|
||||
//bind 不支持
|
||||
request.TCPReply(socks.REP_UNKNOWN)
|
||||
utils.CloseConn(&inConn)
|
||||
return
|
||||
case socks.CMD_CONNECT:
|
||||
//tcp
|
||||
s.proxyTCP(&inConn, methodReq, request)
|
||||
case socks.CMD_ASSOCIATE:
|
||||
//udp
|
||||
s.proxyUDP(&inConn, methodReq, request)
|
||||
}
|
||||
|
||||
}
|
||||
func (s *Socks) proxyUDP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) {
|
||||
if *s.cfg.ParentType == "ssh" {
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
host, _, _ := net.SplitHostPort((*inConn).LocalAddr().String())
|
||||
_, port, _ := net.SplitHostPort(s.udpSC.UDPListener.LocalAddr().String())
|
||||
log.Printf("proxy udp on %s", net.JoinHostPort(host, port))
|
||||
request.UDPReply(socks.REP_SUCCESS, net.JoinHostPort(host, port))
|
||||
}
|
||||
func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) {
|
||||
var outConn net.Conn
|
||||
var err interface{}
|
||||
useProxy := true
|
||||
tryCount := 0
|
||||
maxTryCount := 5
|
||||
//防止死循环
|
||||
if s.IsDeadLoop((*inConn).LocalAddr().String(), request.Host()) {
|
||||
utils.CloseConn(inConn)
|
||||
log.Printf("dead loop detected , %s", request.Host())
|
||||
utils.CloseConn(inConn)
|
||||
return
|
||||
}
|
||||
for {
|
||||
if *s.cfg.Always {
|
||||
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||
} else {
|
||||
if *s.cfg.Parent != "" {
|
||||
s.checker.Add(request.Addr(), true, "", "", nil)
|
||||
useProxy, _, _ = s.checker.IsBlocked(request.Addr())
|
||||
if useProxy {
|
||||
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||
} else {
|
||||
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
||||
}
|
||||
} else {
|
||||
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
||||
useProxy = false
|
||||
}
|
||||
}
|
||||
tryCount++
|
||||
if err == nil || tryCount > maxTryCount || *s.cfg.Parent == "" {
|
||||
break
|
||||
} else {
|
||||
log.Printf("get out conn fail,%s,retrying...", err)
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("get out conn fail,%s", err)
|
||||
request.TCPReply(socks.REP_NETWOR_UNREACHABLE)
|
||||
return
|
||||
}
|
||||
log.Printf("use proxy %v : %s", useProxy, request.Addr())
|
||||
|
||||
request.TCPReply(socks.REP_SUCCESS)
|
||||
inAddr := (*inConn).RemoteAddr().String()
|
||||
//inLocalAddr := (*inConn).LocalAddr().String()
|
||||
|
||||
log.Printf("conn %s - %s connected", inAddr, request.Addr())
|
||||
utils.IoBind(*inConn, outConn, func(err interface{}) {
|
||||
log.Printf("conn %s - %s released", inAddr, request.Addr())
|
||||
})
|
||||
}
|
||||
func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) {
|
||||
switch *s.cfg.ParentType {
|
||||
case "kcp":
|
||||
fallthrough
|
||||
case "tls":
|
||||
fallthrough
|
||||
case "tcp":
|
||||
if *s.cfg.ParentType == "tls" {
|
||||
var _outConn tls.Conn
|
||||
_outConn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
||||
outConn = net.Conn(&_outConn)
|
||||
} else if *s.cfg.ParentType == "kcp" {
|
||||
outConn, err = utils.ConnectKCPHost(*s.cfg.Parent, *s.cfg.KCPMethod, *s.cfg.KCPKey)
|
||||
} else {
|
||||
outConn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("connect fail,%s", err)
|
||||
return
|
||||
}
|
||||
var buf = make([]byte, 1024)
|
||||
//var n int
|
||||
_, err = outConn.Write(methodBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("write method fail,%s", err)
|
||||
return
|
||||
}
|
||||
_, err = outConn.Read(buf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read method reply fail,%s", err)
|
||||
return
|
||||
}
|
||||
//resp := buf[:n]
|
||||
//log.Printf("resp:%v", resp)
|
||||
|
||||
_, err = outConn.Write(reqBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("write req detail fail,%s", err)
|
||||
return
|
||||
}
|
||||
_, err = outConn.Read(buf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read req reply fail,%s", err)
|
||||
return
|
||||
}
|
||||
//result := buf[:n]
|
||||
//log.Printf("result:%v", result)
|
||||
|
||||
case "ssh":
|
||||
maxTryCount := 1
|
||||
tryCount := 0
|
||||
RETRY:
|
||||
if tryCount >= maxTryCount {
|
||||
return
|
||||
}
|
||||
wait := make(chan bool, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = recover()
|
||||
}
|
||||
wait <- true
|
||||
}()
|
||||
outConn, err = s.sshClient.Dial("tcp", host)
|
||||
}()
|
||||
select {
|
||||
case <-wait:
|
||||
case <-time.After(time.Millisecond * time.Duration(*s.cfg.Timeout) * 2):
|
||||
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||
s.sshClient.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||
e := s.ConnectSSH()
|
||||
if e == nil {
|
||||
tryCount++
|
||||
time.Sleep(time.Second * 3)
|
||||
goto RETRY
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
func (s *Socks) ConnectSSH() (err error) {
|
||||
select {
|
||||
case s.lockChn <- true:
|
||||
default:
|
||||
err = fmt.Errorf("can not connect at same time")
|
||||
return
|
||||
}
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: time.Duration(*s.cfg.Timeout) * time.Millisecond,
|
||||
User: *s.cfg.SSHUser,
|
||||
Auth: []ssh.AuthMethod{s.cfg.SSHAuthMethod},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if s.sshClient != nil {
|
||||
s.sshClient.Close()
|
||||
}
|
||||
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
||||
<-s.lockChn
|
||||
return
|
||||
}
|
||||
func (s *Socks) InitBasicAuth() (err error) {
|
||||
s.basicAuth = utils.NewBasicAuth()
|
||||
if *s.cfg.AuthURL != "" {
|
||||
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||
log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||
}
|
||||
if *s.cfg.AuthFile != "" {
|
||||
var n = 0
|
||||
n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||
return
|
||||
}
|
||||
log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
if len(*s.cfg.Auth) > 0 {
|
||||
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||
log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *Socks) IsBasicAuth() bool {
|
||||
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||
}
|
||||
func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
|
||||
inIP, inPort, err := net.SplitHostPort(inLocalAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
outDomain, outPort, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if inPort == outPort {
|
||||
var outIPs []net.IP
|
||||
outIPs, err = net.LookupIP(outDomain)
|
||||
if err == nil {
|
||||
for _, ip := range outIPs {
|
||||
if ip.String() == inIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
interfaceIPs, err := utils.GetAllInterfaceAddr()
|
||||
for _, ip := range *s.cfg.LocalIPS {
|
||||
interfaceIPs = append(interfaceIPs, net.ParseIP(ip).To4())
|
||||
}
|
||||
if err == nil {
|
||||
for _, localIP := range interfaceIPs {
|
||||
for _, outIP := range outIPs {
|
||||
if localIP.Equal(outIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||