Compare commits
807 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31fa3f021a | ||
|
|
01a0d557ef | ||
|
|
b9c24e9b69 | ||
|
|
df12cc2b9d | ||
|
|
7cc67e852e | ||
|
|
307d1bfa3f | ||
|
|
5eb8f3db03 | ||
|
|
2d3af8a108 | ||
|
|
3ae1a4f45a | ||
|
|
21d8e674f0 | ||
|
|
5e70d5bee0 | ||
|
|
5c8ea51eb5 | ||
|
|
bae0b4d7c0 | ||
|
|
74255f711e | ||
|
|
7cd02f5bd8 | ||
|
|
c95311d1a0 | ||
|
|
885b029fcf | ||
|
|
466d69eae0 | ||
|
|
f1454e91f5 | ||
|
|
7c8cbeb250 | ||
|
|
e9e12cf888 | ||
|
|
6430afcfa5 | ||
|
|
3235addaaa | ||
|
|
46ff40543a | ||
|
|
4fd6301577 | ||
|
|
efcc028a3d | ||
|
|
90861b6821 | ||
|
|
8f105adbca | ||
|
|
53626b370c | ||
|
|
b1789afbab | ||
|
|
88c7e8bf7c | ||
|
|
fc4e787fe2 | ||
|
|
4c4d5f0d0d | ||
|
|
801e8c6742 | ||
|
|
4fd800bc48 | ||
|
|
b146989703 | ||
|
|
685d7618f3 | ||
|
|
15a245766e | ||
|
|
e1cef053be | ||
|
|
9ba6a06470 | ||
|
|
ea08de668e | ||
|
|
de85c9455a | ||
|
|
cceab7e1b1 | ||
|
|
9aef3b9944 | ||
|
|
341a5e3e3a | ||
|
|
c7a0cfc66d | ||
|
|
555db9d272 | ||
|
|
0d6d968fe8 | ||
|
|
98068402c8 | ||
|
|
4915852b9c | ||
|
|
756dd1ad5e | ||
|
|
c71efde303 | ||
|
|
9f029e3248 | ||
|
|
8095075719 | ||
|
|
2225a1781f | ||
|
|
0214b974dd | ||
|
|
738c53ce47 | ||
|
|
db52f07d34 | ||
|
|
f6b8645f56 | ||
|
|
2c2c4ecdbc | ||
|
|
3faae194d0 | ||
|
|
a22d6c9504 | ||
|
|
9800b4cfcf | ||
|
|
8f394dba27 | ||
|
|
fccd518512 | ||
|
|
8fb99ef7a9 | ||
|
|
968ba4d3a1 | ||
|
|
862b1642ba | ||
|
|
54eb704650 | ||
|
|
8c6303c1e5 | ||
|
|
871511ba52 | ||
|
|
cb6d7ba7f9 | ||
|
|
31f40aa913 | ||
|
|
2f59e967a0 | ||
|
|
fe8374e99b | ||
|
|
24f0b3afa5 | ||
|
|
39941117b6 | ||
|
|
6a1f9ad893 | ||
|
|
88e74ff24d | ||
|
|
18ab58eb25 | ||
|
|
534dc99d55 | ||
|
|
fa0593ae2c | ||
|
|
89fff7d11d | ||
|
|
38d42dbe4b | ||
|
|
aa31d7ad0b | ||
|
|
113e3b0b0d | ||
|
|
100148d925 | ||
|
|
6b3daffaf0 | ||
|
|
5e17bc7bf1 | ||
|
|
b1b8d9a82b | ||
|
|
24c7d1d9e2 | ||
|
|
d205c26480 | ||
|
|
0eecab06c1 | ||
|
|
ad3548d332 | ||
|
|
595aba5a9b | ||
|
|
679992db25 | ||
|
|
5cfbb976f4 | ||
|
|
b03f0ad1e6 | ||
|
|
804f2910fd | ||
|
|
a4189ba474 | ||
|
|
e2d28d9929 | ||
|
|
9ec84f8143 | ||
|
|
7678938c08 | ||
|
|
b2e3946800 | ||
|
|
af0b7939a7 | ||
|
|
2f66dc3e99 | ||
|
|
649df8827c | ||
|
|
da51adc276 | ||
|
|
e5af37bc8c | ||
|
|
8ab474cc97 | ||
|
|
e8c8d5903a | ||
|
|
a301046f3d | ||
|
|
34ab6b0e74 | ||
|
|
cf66ca10b4 | ||
|
|
3fbe6b659e | ||
|
|
6a71d71e58 | ||
|
|
6ecc97c857 | ||
|
|
ba492f07c3 | ||
|
|
9d077b02cf | ||
|
|
f4e4fbea62 | ||
|
|
3e721d122b | ||
|
|
1bc899ec12 | ||
|
|
6f2571980c | ||
|
|
8888610d83 | ||
|
|
fa7c05c617 | ||
|
|
218b354f82 | ||
|
|
c652b8ef07 | ||
|
|
5b8b145577 | ||
|
|
fe5fb0326b | ||
|
|
0711295b0a | ||
|
|
4af85da0c2 | ||
|
|
bd89eaba2f | ||
|
|
a72259c604 | ||
|
|
44eb513f05 | ||
|
|
eb1e19a821 | ||
|
|
6c658586f6 | ||
|
|
888ed25314 | ||
|
|
21240ed962 | ||
|
|
6481870d03 | ||
|
|
a7a4ba270d | ||
|
|
915d9f4c09 | ||
|
|
18a2af4703 | ||
|
|
305e40fa8a | ||
|
|
10f2620131 | ||
|
|
4acae540c8 | ||
|
|
11b13533a0 | ||
|
|
100d556336 | ||
|
|
452fe25cc6 | ||
|
|
63efa6b776 | ||
|
|
37c27169ac | ||
|
|
ce677820c6 | ||
|
|
1f88a7a0b8 | ||
|
|
eeea7602d9 | ||
|
|
bf635c0e90 | ||
|
|
cd31359a27 | ||
|
|
19739ed31a | ||
|
|
10100c28d9 | ||
|
|
88fcc079e8 | ||
|
|
ddc1e163c4 | ||
|
|
d20a6d3d75 | ||
|
|
6194273615 | ||
|
|
b2311e55e7 | ||
|
|
07873d471f | ||
|
|
2dab5d0bca | ||
|
|
9ca2b586f8 | ||
|
|
e59eacb8a2 | ||
|
|
0db4fc07fb | ||
|
|
70f4caac23 | ||
|
|
293003fcdb | ||
|
|
4bfc89d988 | ||
|
|
22412851b4 | ||
|
|
e9775bd70f | ||
|
|
ff7b8b0b62 | ||
|
|
491c1d7dc4 | ||
|
|
ea568e8a4f | ||
|
|
0fb6aeef58 | ||
|
|
032f33fe5a | ||
|
|
bbc8b438d5 | ||
|
|
05b1ace21f | ||
|
|
cbdd73b94f | ||
|
|
bf06e3b107 | ||
|
|
143750901e | ||
|
|
71489d194c | ||
|
|
85aa3df256 | ||
|
|
f1a51eba18 | ||
|
|
1d26ea440b | ||
|
|
998e678a7f | ||
|
|
0cee1877e3 | ||
|
|
72a7fd948e | ||
|
|
357c9b0dcb | ||
|
|
14bd0716d0 | ||
|
|
2f74f54f18 | ||
|
|
a62a9431b1 | ||
|
|
42745a3da2 | ||
|
|
82f80a22be | ||
|
|
f570dcb307 | ||
|
|
997d406ec2 | ||
|
|
87e60683ed | ||
|
|
86b2e686a5 | ||
|
|
09f39de74e | ||
|
|
2a68c1152f | ||
|
|
df5859b5f7 | ||
|
|
3dd888a9ea | ||
|
|
a51e221db3 | ||
|
|
fe4e9b55f3 | ||
|
|
3f11b6a082 | ||
|
|
8a333c2ae0 | ||
|
|
1fd6ba2738 | ||
|
|
a98a9616f6 | ||
|
|
95cd9ab900 | ||
|
|
900454e58b | ||
|
|
c7d4637382 | ||
|
|
56925961df | ||
|
|
cfd1a3128a | ||
|
|
2393923870 | ||
|
|
5f594e9a71 | ||
|
|
57577ea044 | ||
|
|
8637077d90 | ||
|
|
ccb85a9926 | ||
|
|
02b12df887 | ||
|
|
c32a2ed140 | ||
|
|
9ae322cccf | ||
|
|
9cebfccb39 | ||
|
|
630dad50ed | ||
|
|
0d84da91d4 | ||
|
|
2408f1df04 | ||
|
|
fbaa5f866e | ||
|
|
c5c79e4148 | ||
|
|
9a849a29e9 | ||
|
|
6b80861bd6 | ||
|
|
fa0e84382e | ||
|
|
1a11b28f8d | ||
|
|
bed13d7ef1 | ||
|
|
e7d76b180d | ||
|
|
dba8925eaa | ||
|
|
55da58eca4 | ||
|
|
fdef7448a7 | ||
|
|
0ff27fc9ac | ||
|
|
76a1efccd9 | ||
|
|
9f8db314d6 | ||
|
|
980f084ad1 | ||
|
|
0c35863d97 | ||
|
|
184a0ff9ab | ||
|
|
8e25f13201 | ||
|
|
b5aee82ca9 | ||
|
|
0a2384a283 | ||
|
|
78b8bb7bc6 | ||
|
|
8fcd4f4a95 | ||
|
|
976fd81d4d | ||
|
|
52d5c9e25b | ||
|
|
fa89671452 | ||
|
|
3621aad1c1 | ||
|
|
3bf1eb8565 | ||
|
|
a821db3f45 | ||
|
|
ecb6ed9258 | ||
|
|
b2ae433e18 | ||
|
|
b26080589b | ||
|
|
aff979c2b6 | ||
|
|
46f809d711 | ||
|
|
72595b2da8 | ||
|
|
c842558ace | ||
|
|
ed61049041 | ||
|
|
abe6f580c0 | ||
|
|
e940066012 | ||
|
|
1e846df870 | ||
|
|
0ab055e946 | ||
|
|
fca59c71e2 | ||
|
|
fae2f8768d | ||
|
|
3d9499f554 | ||
|
|
7adeeedd55 | ||
|
|
127a31ea6a | ||
|
|
a85bd9a4d9 | ||
|
|
01d551ec8d | ||
|
|
16cabf4127 | ||
|
|
968be4a2c2 | ||
|
|
aa0a41ee4e | ||
|
|
8a779eb88c | ||
|
|
0138dbd352 | ||
|
|
9b45c93c14 | ||
|
|
191da54980 | ||
|
|
c3b7575453 | ||
|
|
1ea1530b36 | ||
|
|
7f7305fa03 | ||
|
|
644a0cfdb6 | ||
|
|
3c2e2bcea5 | ||
|
|
e0c45a1aca | ||
|
|
e52dfc4a5c | ||
|
|
cc003a2570 | ||
|
|
0f8040b875 | ||
|
|
ef5ae3e598 | ||
|
|
3acf1bb6e9 | ||
|
|
1089eb9d22 | ||
|
|
edf9596ca8 | ||
|
|
26e54b901f | ||
|
|
008933f304 | ||
|
|
317f901c1c | ||
|
|
cd5314466c | ||
|
|
3fbdea0f6b | ||
|
|
710ecf44f5 | ||
|
|
04dafd7ff0 | ||
|
|
c6aa74a2bb | ||
|
|
813c45f5c2 | ||
|
|
c0e05bb41e | ||
|
|
aa74dc4646 | ||
|
|
1e420cc766 | ||
|
|
4fff3c7472 | ||
|
|
48fa618c34 | ||
|
|
c9fe23eb10 | ||
|
|
268afb3438 | ||
|
|
b1181fd17a | ||
|
|
b23548eeff | ||
|
|
262317192c | ||
|
|
8b75b8b837 | ||
|
|
2170c481ce | ||
|
|
dfbf9c4542 | ||
|
|
964a1bbf39 | ||
|
|
228e225f84 | ||
|
|
bd6435c982 | ||
|
|
591023a1f0 | ||
|
|
1ab23b5e0e | ||
|
|
d193519329 | ||
|
|
2406ecdfea | ||
|
|
7266154d54 | ||
|
|
4797136965 | ||
|
|
6d78af6144 | ||
|
|
7728e35c52 | ||
|
|
5a61fd84ad | ||
|
|
ad0c449a75 | ||
|
|
1c330185c4 | ||
|
|
8668fef136 | ||
|
|
7491b327f8 | ||
|
|
abb5b05d49 | ||
|
|
b6ec9dad28 | ||
|
|
caa6e8cf01 | ||
|
|
ffb932390f | ||
|
|
a8efaee1f3 | ||
|
|
4c2afb5c28 | ||
|
|
809f517db8 | ||
|
|
a4b105dedb | ||
|
|
10acf638f8 | ||
|
|
ea62bc5a34 | ||
|
|
f65ffe2812 | ||
|
|
23bb76397a | ||
|
|
859a330e6c | ||
|
|
86ac511763 | ||
|
|
f2e98ef8a4 | ||
|
|
495d999b6c | ||
|
|
6d1af85e80 | ||
|
|
1db091b381 | ||
|
|
0b9124d4fd | ||
|
|
6c6607ae68 | ||
|
|
83d80857fd | ||
|
|
98fa3855bd | ||
|
|
9440bc5d72 | ||
|
|
95753ebf1c | ||
|
|
f8c6795119 | ||
|
|
7033f3e72b | ||
|
|
e3101b7aa8 | ||
|
|
c747f160aa | ||
|
|
c8748a2948 | ||
|
|
487c8d7c29 | ||
|
|
69fa7ed16e | ||
|
|
5336155365 | ||
|
|
4feb74cb89 | ||
|
|
4a4cf552af | ||
|
|
0f59b8f329 | ||
|
|
f480160e2d | ||
|
|
4832a2a1e9 | ||
|
|
52ecd84d8a | ||
|
|
30c246c488 | ||
|
|
42014eea23 | ||
|
|
c2da396230 | ||
|
|
e91c9473be | ||
|
|
13e48c6ca0 | ||
|
|
31e2cb76bb | ||
|
|
91e46a2c53 | ||
|
|
a57679f837 | ||
|
|
75f3bce04d | ||
|
|
df18375308 | ||
|
|
c63737ab3e | ||
|
|
1cdceee347 | ||
|
|
694c434b9e | ||
|
|
62af5c8844 | ||
|
|
56c53909aa | ||
|
|
21a126e4e4 | ||
|
|
8affab1a2b | ||
|
|
12cc53d699 | ||
|
|
2ab832bb89 | ||
|
|
42425d8218 | ||
|
|
6da093a402 | ||
|
|
adc3adc13b | ||
|
|
22a79710d8 | ||
|
|
0927553fe4 | ||
|
|
858d8f0ba7 | ||
|
|
8eb945ee9b | ||
|
|
dc0fd60d30 | ||
|
|
cd44c9f55c | ||
|
|
649f47c345 | ||
|
|
6ca3160b33 | ||
|
|
5f8ed4fc60 | ||
|
|
bf0993d2a6 | ||
|
|
5dc8175fc8 | ||
|
|
dc6a5a29c1 | ||
|
|
e62d9a5242 | ||
|
|
94212ac8b8 | ||
|
|
e9e86fccf0 | ||
|
|
58745992ef | ||
|
|
234d634bfe | ||
|
|
fdc6902a90 | ||
|
|
d8d587fd93 | ||
|
|
92791260a7 | ||
|
|
4dfd851c46 | ||
|
|
bc4df74b5e | ||
|
|
666f122a72 | ||
|
|
f999c8a87e | ||
|
|
90a32ab75d | ||
|
|
0713fd28da | ||
|
|
f5b33e6de8 | ||
|
|
fc6043bb4d | ||
|
|
bc46e3330a | ||
|
|
5fc7b3ceb5 | ||
|
|
6277af4790 | ||
|
|
00bd0a8af4 | ||
|
|
a415573e45 | ||
|
|
e68012858e | ||
|
|
ca8a5b753c | ||
|
|
d1f4ac0f2d | ||
|
|
ff357882ac | ||
|
|
934ac2b836 | ||
|
|
1ad50d5982 | ||
|
|
388b016842 | ||
|
|
134a46c00b | ||
|
|
50796643fb | ||
|
|
b1838b1d5e | ||
|
|
757b3613fe | ||
|
|
ae08811636 | ||
|
|
b657c0fe09 | ||
|
|
84df71047c | ||
|
|
abc6d720d0 | ||
|
|
80154639e3 | ||
|
|
f2117d8331 | ||
|
|
261be6a7b7 | ||
|
|
b53a2c1ed9 | ||
|
|
ee0df07a3c | ||
|
|
4e363eca2b | ||
|
|
4277405c0e | ||
|
|
6a99f0caf7 | ||
|
|
394af08561 | ||
|
|
6451583e60 | ||
|
|
30cb0a3ab0 | ||
|
|
5680a88267 | ||
|
|
6b089858db | ||
|
|
b3ed863021 | ||
|
|
5796c27ed5 | ||
|
|
310e8dd768 | ||
|
|
0b40ac2dbc | ||
|
|
f22c8e0882 | ||
|
|
a388bb2c95 | ||
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 | ||
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 | ||
|
|
a8ab4c5003 | ||
|
|
9cee263c91 | ||
|
|
c6bf6f59e6 | ||
|
|
4b7aef2196 | ||
|
|
f6d0046b5a | ||
|
|
84363266d2 | ||
|
|
9ac8f2a047 | ||
|
|
b2b55533b8 | ||
|
|
a4cfab689a | ||
|
|
c7df39074c | ||
|
|
fdcdccb0c2 | ||
|
|
e945c1667a | ||
|
|
87a4de4370 | ||
|
|
e1e2913b77 | ||
|
|
9be24db410 | ||
|
|
6b61cb3742 | ||
|
|
90b7f2080f | ||
|
|
d1f1c72a55 | ||
|
|
1925847ef8 | ||
|
|
8b216b0ca9 | ||
|
|
dbfeea99f3 | ||
|
|
5e64bbfa7c | ||
|
|
e691a40260 | ||
|
|
d812488767 | ||
|
|
3c03690ab7 | ||
|
|
3df27b9c04 | ||
|
|
ba45d29b7c | ||
|
|
3cf83f57a8 | ||
|
|
03e4318d79 | ||
|
|
178d134f46 | ||
|
|
cbf9c731a0 | ||
|
|
de4bfcc43c | ||
|
|
9737978f28 | ||
|
|
5bc7fe2cea | ||
|
|
65d8fe37c5 | ||
|
|
1723d7b651 | ||
|
|
2481dfab64 | ||
|
|
95a881a7d3 | ||
|
|
fe403ab328 | ||
|
|
66555dbb00 | ||
|
|
7f9ea48405 | ||
|
|
96d7e2da6f | ||
|
|
d879b8208b | ||
|
|
3585e456d4 | ||
|
|
1de8c3fc87 | ||
|
|
bbab3fe9ca | ||
|
|
48990da22e | ||
|
|
5543fc2a9a | ||
|
|
c41de6fd28 | ||
|
|
8c8fd9790e | ||
|
|
5a7ef3be74 | ||
|
|
d9b5e0bde0 | ||
|
|
05ca72dbf0 | ||
|
|
ef6f8bbf6c | ||
|
|
70ac7d3d11 | ||
|
|
385c4d3dd5 | ||
|
|
5e1983f7ed | ||
|
|
516cdbddb0 | ||
|
|
3954ceb93b | ||
|
|
2061ef11c8 | ||
|
|
71cbe5decc | ||
|
|
a2ccb6c190 | ||
|
|
5bdf530b7e | ||
|
|
5177570da4 | ||
|
|
0bd8f9cd9b | ||
|
|
649a2f2457 | ||
|
|
54916793f9 | ||
|
|
0b06c1c821 | ||
|
|
bbc6f1687d | ||
|
|
b250342e27 | ||
|
|
f76deb8898 | ||
|
|
611d063e1f | ||
|
|
0c7d778896 | ||
|
|
7c21906884 | ||
|
|
a4106ec4b7 | ||
|
|
655c52f9ce | ||
|
|
25cfda5768 | ||
|
|
b61cb14c8f | ||
|
|
d5ce4d4916 | ||
|
|
4f0ee5980d | ||
|
|
146956ac6e | ||
|
|
35278ad17f | ||
|
|
aea9f9fbcc | ||
|
|
08c17c3247 | ||
|
|
6934a18f95 | ||
|
|
5165b0821f | ||
|
|
0aec869513 | ||
|
|
826b9db5f2 | ||
|
|
89d1a1fb2b | ||
|
|
450e0b7148 | ||
|
|
a1ac002694 | ||
|
|
951d33d47c | ||
|
|
b33ea9274c | ||
|
|
3b3f3dc2b5 | ||
|
|
ec0b59732c | ||
|
|
bae1ecdc69 | ||
|
|
5c2ab5a749 | ||
|
|
1a8ac148ca | ||
|
|
698219b621 | ||
|
|
229740524e | ||
|
|
cbeeac06a5 | ||
|
|
66a69f873f | ||
|
|
fb13774457 | ||
|
|
f14ed87b29 | ||
|
|
07623027bc | ||
|
|
941ac25648 | ||
|
|
f645082d72 | ||
|
|
7793f55545 | ||
|
|
ca88b07ecf | ||
|
|
6e305db4be | ||
|
|
9bb08396c7 | ||
|
|
64136a3b3e | ||
|
|
b8037475ed | ||
|
|
082447f517 | ||
|
|
cc6486addb | ||
|
|
57417c83ae | ||
|
|
d74b45be5d | ||
|
|
0d02f291e3 | ||
|
|
42ee536dae | ||
|
|
c33b5152e7 | ||
|
|
b6c219aa97 | ||
|
|
bbc36be052 | ||
|
|
f5778349d5 | ||
|
|
71603c6d0b | ||
|
|
e64fcce417 | ||
|
|
629f2856b1 | ||
|
|
aeb9f2b64d | ||
|
|
85dd41c17b | ||
|
|
102408d37f | ||
|
|
495b577819 | ||
|
|
f56b49ad3b | ||
|
|
cb1bf71bef | ||
|
|
b9f062bef2 | ||
|
|
490019fb51 | ||
|
|
2e497274ba | ||
|
|
cf4136fe99 | ||
|
|
b1e9cff622 | ||
|
|
db2d1fce76 | ||
|
|
8579de9d3f | ||
|
|
0c35273759 | ||
|
|
6eb8146334 | ||
|
|
da78e3f52e | ||
|
|
e1918f6396 | ||
|
|
ad1e32fd2d | ||
|
|
3726f99b04 | ||
|
|
c8a7405992 | ||
|
|
040d198e36 | ||
|
|
1a6cbbb2d2 | ||
|
|
ea79e03bd0 | ||
|
|
3e349455a0 | ||
|
|
c7a457a045 | ||
|
|
0b0d5c982e | ||
|
|
c4f873c07a | ||
|
|
01b1df2b91 | ||
|
|
f1bea49314 | ||
|
|
2ffae3489b | ||
|
|
96b94d9164 | ||
|
|
76b04f52d1 | ||
|
|
97db0d187a | ||
|
|
d9aadab4cb | ||
|
|
a0fe2fc2c2 | ||
|
|
1464836f05 | ||
|
|
b2a2037032 | ||
|
|
071cbf4b15 | ||
|
|
20fcb58437 | ||
|
|
a27e3dda88 | ||
|
|
1dd7317c06 | ||
|
|
58a54bd0fb | ||
|
|
caec4982cc | ||
|
|
dd8f788ca4 | ||
|
|
8a6d6c534a | ||
|
|
39089cf262 | ||
|
|
55800dc29f | ||
|
|
04560c1896 | ||
|
|
178efd67f1 | ||
|
|
6ef5fb6391 | ||
|
|
1ae43e4d41 | ||
|
|
5db605ca02 | ||
|
|
e087301425 | ||
|
|
f45283dbdb | ||
|
|
b0959b3caa | ||
|
|
c5c89a2519 | ||
|
|
bebd1db22a | ||
|
|
cd37d22f3b | ||
|
|
30af32728a | ||
|
|
60ecd1d58c | ||
|
|
a60be8f562 | ||
|
|
c008b14d0f | ||
|
|
853892f3cd | ||
|
|
e43f9f5850 | ||
|
|
d5f30ccd6b | ||
|
|
b87df569e7 | ||
|
|
976cf3e9f8 | ||
|
|
371c401f5b | ||
|
|
69919e8ef9 | ||
|
|
9abbe33790 | ||
|
|
4a5c00286e | ||
|
|
dfb892c8f6 | ||
|
|
461c4c18fd | ||
|
|
00b9ba95ae | ||
|
|
c47aad348d | ||
|
|
4cb4da3afc | ||
|
|
c1f57da00d | ||
|
|
fe187eb8ec | ||
|
|
0f6f674a64 | ||
|
|
814afbe1f6 | ||
|
|
3fde9176c9 | ||
|
|
af7cca1a93 | ||
|
|
7dd28a14aa | ||
|
|
1325c59a4c | ||
|
|
82dc1e924f | ||
|
|
3166bdf3f0 | ||
|
|
8af70c8822 | ||
|
|
87763e8251 | ||
|
|
e9241aeb94 | ||
|
|
2eaf134042 | ||
|
|
1739e012d6 | ||
|
|
9e8980429f | ||
|
|
1d0865ca49 | ||
|
|
5c9909aeef | ||
|
|
456ce09061 | ||
|
|
ffc13b704a | ||
|
|
5d239127bb | ||
|
|
9b990adf96 | ||
|
|
44e8108910 | ||
|
|
1c35e9a0c6 | ||
|
|
8e719ff0ff | ||
|
|
637ddbce1f | ||
|
|
ce8fde793c | ||
|
|
eede31c064 | ||
|
|
41c41789b6 | ||
|
|
68dfc89bce | ||
|
|
8690075c0c | ||
|
|
33d8816ced | ||
|
|
90cd25ac21 | ||
|
|
ff28668cf2 | ||
|
|
a6f2736b80 | ||
|
|
902f6f84a5 | ||
|
|
cf9193a429 | ||
|
|
3f64d73ea9 | ||
|
|
a77c7e8625 | ||
|
|
14733dd109 | ||
|
|
74b75e8c57 | ||
|
|
63e6e0dc92 | ||
|
|
4d4a738aa9 | ||
|
|
1ed130e704 | ||
|
|
2e773d550b | ||
|
|
e155ff056e | ||
|
|
37210d9983 | ||
|
|
338d5bae37 | ||
|
|
3e62198612 | ||
|
|
4f7dfcdb31 | ||
|
|
5b08201e5d | ||
|
|
b2c846664d | ||
|
|
3f6799c06a | ||
|
|
9a5f0c23c4 | ||
|
|
afde0c515c | ||
|
|
584e098e8e | ||
|
|
37395b3ef5 | ||
|
|
43fb3f3ff7 | ||
|
|
82b127494c | ||
|
|
4d79648657 | ||
|
|
3bb404dfb5 | ||
|
|
ff4bdec3f7 | ||
|
|
69f8b08ac0 | ||
|
|
d873df5ca8 | ||
|
|
a384bf5580 | ||
|
|
92046a7ca2 | ||
|
|
4cc5ddc012 | ||
|
|
46358d466d | ||
|
|
7da61f004b | ||
|
|
63037f1c65 | ||
|
|
cc160995da | ||
|
|
de48d97cb2 | ||
|
|
1a6a179b68 | ||
|
|
3a2946a2ff | ||
|
|
ae9a4623d9 | ||
|
|
bd1e9a3010 | ||
|
|
92fff5c191 | ||
|
|
8c65b337ca | ||
|
|
0f1005ff61 | ||
|
|
ad858a0d32 | ||
|
|
1e905839f0 | ||
|
|
bf50f932d9 | ||
|
|
673047be2c | ||
|
|
fa2b9a836c | ||
|
|
9e0fd0c4ef | ||
|
|
0559865fe5 | ||
|
|
4fc85a36c2 | ||
|
|
3f1174a519 | ||
|
|
bcbdfcb99b | ||
|
|
df046bdeeb | ||
|
|
f83447c652 | ||
|
|
9ae69b4aac | ||
|
|
c48a89731a | ||
|
|
36b58ab60c | ||
|
|
6320f15a7c | ||
|
|
066172e9c1 | ||
|
|
d5931758b6 | ||
|
|
c75c3acd21 | ||
|
|
0208ecd1d9 | ||
|
|
23e9845e65 | ||
|
|
2b1ba3a946 | ||
|
|
ee9ddf52cd | ||
|
|
d246400a71 | ||
|
|
f63a4f0cdd | ||
|
|
b743b5aaed | ||
|
|
9d9416ab94 | ||
|
|
c081df40e1 | ||
|
|
fe32a7c4bb | ||
|
|
7bb8c10647 | ||
|
|
0752508469 | ||
|
|
4cc1663a5f | ||
|
|
b55a24a27e | ||
|
|
aede4e54f8 | ||
|
|
b811a620c3 | ||
|
|
07fe05a9d5 | ||
|
|
171bc8dd22 | ||
|
|
9c175d4eb5 | ||
|
|
9f736558e2 | ||
|
|
8f071dd2c2 | ||
|
|
bcaf51a6ad |
25
.circleci/config.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: 2
|
||||
jobs:
|
||||
go-version-latest:
|
||||
docker:
|
||||
- image: cimg/go:1.21-node
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make alltest
|
||||
go-version-last:
|
||||
docker:
|
||||
- image: cimg/go:1.20-node
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make alltest
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- go-version-latest
|
||||
- go-version-last
|
||||
@@ -1,5 +0,0 @@
|
||||
Dockerfile
|
||||
.git
|
||||
*~
|
||||
*#
|
||||
.#*
|
||||
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [fatedier]
|
||||
custom: ["https://afdian.net/a/fatedier"]
|
||||
30
.github/ISSUE_TEMPLATE
vendored
@@ -1,30 +0,0 @@
|
||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
|
||||
**What version of frp are you using (./frpc -v or ./frps -v)?**
|
||||
|
||||
|
||||
**What operating system and processor architecture are you using (`go env`)?**
|
||||
|
||||
|
||||
**Configures you used:**
|
||||
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
**Additional information you deem important (e.g. issue happens only occasionally):**
|
||||
|
||||
|
||||
**Can you point out what caused this issue (optional)**
|
||||
77
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Bug report
|
||||
description: Report a bug to help us improve frp
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: Tell us what issues you ran into
|
||||
placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: frpc-version
|
||||
attributes:
|
||||
label: frpc Version
|
||||
description: Include the output of `frpc -v`
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: frps-version
|
||||
attributes:
|
||||
label: frps Version
|
||||
description: Include the output of `frps -v`
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: system-architecture
|
||||
attributes:
|
||||
label: System Architecture
|
||||
description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Configurations
|
||||
description: Include what configurrations you used and ran into this problem
|
||||
placeholder: Pay attention to hiding the token and password in your output
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Prefer you providing releated error logs here
|
||||
placeholder: Pay attention to hiding your personal informations
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: How to reproduce it? It's important for us to find the bug
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
- type: checkboxes
|
||||
id: area
|
||||
attributes:
|
||||
label: Affected area
|
||||
options:
|
||||
- label: "Docs"
|
||||
- label: "Installation"
|
||||
- label: "Performance and Scalability"
|
||||
- label: "Security"
|
||||
- label: "User Experience"
|
||||
- label: "Test and Release"
|
||||
- label: "Developer Infrastructure"
|
||||
- label: "Client Plugin"
|
||||
- label: "Server Plugin"
|
||||
- label: "Extensions"
|
||||
- label: "Others"
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea to improve frp
|
||||
title: "[Feature Request] "
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This is only used to request new product features.
|
||||
- type: textarea
|
||||
id: feature-request
|
||||
attributes:
|
||||
label: Describe the feature request
|
||||
description: Tell us what's you want and why it should be added in frp.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
- type: checkboxes
|
||||
id: area
|
||||
attributes:
|
||||
label: Affected area
|
||||
options:
|
||||
- label: "Docs"
|
||||
- label: "Installation"
|
||||
- label: "Performance and Scalability"
|
||||
- label: "Security"
|
||||
- label: "User Experience"
|
||||
- label: "Test and Release"
|
||||
- label: "Developer Infrastructure"
|
||||
- label: "Client Plugin"
|
||||
- label: "Server Plugin"
|
||||
- label: "Extensions"
|
||||
- label: "Others"
|
||||
6
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
### Summary
|
||||
|
||||
copilot:summary
|
||||
|
||||
### WHY
|
||||
<!-- author to complete -->
|
||||
83
.github/workflows/build-and-push-image.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Build Image and Publish to Dockerhub & GPR
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Image tag'
|
||||
required: true
|
||||
default: 'test'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
image:
|
||||
name: Build Image from Dockerfile and binaries
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# get image tag name
|
||||
- name: Get Image Tag Name
|
||||
run: |
|
||||
if [ x${{ github.event.inputs.tag }} == x"" ]; then
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to the GPR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GPR_TOKEN }}
|
||||
|
||||
# prepare image tags
|
||||
- name: Prepare Image Tags
|
||||
run: |
|
||||
echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV
|
||||
echo "TAG_FRPC=fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push frpc
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./dockerfiles/Dockerfile-for-frpc
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.TAG_FRPC }}
|
||||
${{ env.TAG_FRPC_GPR }}
|
||||
|
||||
- name: Build and push frps
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./dockerfiles/Dockerfile-for-frps
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.TAG_FRPS }}
|
||||
${{ env.TAG_FRPS_GPR }}
|
||||
41
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
||||
pull-requests: read
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.53
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||
# takes precedence over all other caching options.
|
||||
# skip-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
||||
30
.github/workflows/goreleaser.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Make All
|
||||
run: |
|
||||
./package.sh
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --release-notes=./Release.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
||||
34
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: "Close stale issues"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "20 0 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug-only:
|
||||
description: 'In debug mod'
|
||||
required: false
|
||||
default: 'false'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write # for actions/stale to close stale issues
|
||||
pull-requests: write # for actions/stale to close stale PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||
stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
||||
stale-issue-label: 'lifecycle/stale'
|
||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
stale-pr-label: 'lifecycle/stale'
|
||||
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
debug-only: ${{ github.event.inputs.debug-only }}
|
||||
exempt-all-pr-milestones: true
|
||||
exempt-all-pr-assignees: true
|
||||
6
.gitignore
vendored
@@ -26,7 +26,13 @@ _testmain.go
|
||||
# Self
|
||||
bin/
|
||||
packages/
|
||||
release/
|
||||
test/bin/
|
||||
vendor/
|
||||
lastversion/
|
||||
dist/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
||||
146
.golangci.yml
Normal file
@@ -0,0 +1,146 @@
|
||||
service:
|
||||
golangci-lint-version: 1.51.x # use the fixed version to not introduce new linters unexpectedly
|
||||
|
||||
run:
|
||||
concurrency: 4
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 20m
|
||||
build-tags:
|
||||
- integ
|
||||
- integfuzz
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs:
|
||||
- genfiles$
|
||||
- vendor$
|
||||
- bin$
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- ".*\\.pb\\.go"
|
||||
- ".*\\.gen\\.go"
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- unused
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- goimports
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- gci
|
||||
- gosec
|
||||
- asciicheck
|
||||
- prealloc
|
||||
- predeclared
|
||||
- makezero
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- cancelled
|
||||
- marshalled
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 160
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- exitAfterDefer
|
||||
unused:
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/fatedier/frp/)
|
||||
gosec:
|
||||
severity: "low"
|
||||
confidence: "low"
|
||||
excludes:
|
||||
- G102
|
||||
- G112
|
||||
- G306
|
||||
- G401
|
||||
- G402
|
||||
- G404
|
||||
- G501
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
# exclude:
|
||||
# - composite literal uses unkeyed fields
|
||||
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on test files.
|
||||
- path: _test\.go$|^tests/|^samples/
|
||||
linters:
|
||||
- errcheck
|
||||
- maligned
|
||||
- linters:
|
||||
- revive
|
||||
- stylecheck
|
||||
text: "use underscores in Go names"
|
||||
- linters:
|
||||
- revive
|
||||
text: "unused-parameter"
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: true
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
22
.goreleaser.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
builds:
|
||||
- skip: true
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||
algorithm: sha256
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
release:
|
||||
# Same as for github
|
||||
# Note: it can only be one: either github, gitlab or gitea
|
||||
github:
|
||||
owner: fatedier
|
||||
name: frp
|
||||
|
||||
draft: false
|
||||
|
||||
# You can add extra pre-existing files to the release.
|
||||
# The filename on the release will be the last part of the path (base). If
|
||||
# another file with the same name exists, the latest one found will be used.
|
||||
# Defaults to empty.
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
11
.travis.yml
@@ -1,11 +0,0 @@
|
||||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
||||
script:
|
||||
- make alltest
|
||||
17
Dockerfile
@@ -1,17 +0,0 @@
|
||||
FROM golang:1.8
|
||||
|
||||
COPY . /go/src/github.com/fatedier/frp
|
||||
|
||||
RUN cd /go/src/github.com/fatedier/frp \
|
||||
&& make \
|
||||
&& mv bin/frpc /frpc \
|
||||
&& mv bin/frps /frps \
|
||||
&& mv conf/frpc_min.ini /frpc.ini \
|
||||
&& mv conf/frps_min.ini /frps.ini \
|
||||
&& make clean
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM alpine:3.5
|
||||
|
||||
COPY tmp/frpc /frpc
|
||||
COPY tmp/frps /frps
|
||||
COPY conf/frpc_min.ini /frpc.ini
|
||||
COPY conf/frps_min.ini /frps.ini
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 80 443 6000 7000 7500
|
||||
|
||||
ENTRYPOINT ["/frps"]
|
||||
126
Godeps/Godeps.json
generated
@@ -1,126 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/fatedier/frp",
|
||||
"GoVersion": "go1.8",
|
||||
"GodepVersion": "v79",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Comment": "v1.1.0",
|
||||
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docopt/docopt-go",
|
||||
"Comment": "0.6.2",
|
||||
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatedier/beego/logs",
|
||||
"Comment": "v1.7.2-72-gf73c369",
|
||||
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/snappy",
|
||||
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||
"Comment": "v1.1-41-g8a45e95",
|
||||
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/cpuid",
|
||||
"Comment": "v1.0",
|
||||
"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/klauspost/reedsolomon",
|
||||
"Comment": "1.3-1-gdde6ad5",
|
||||
"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pkg/errors",
|
||||
"Comment": "v0.8.0-5-gc605e28",
|
||||
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rakyll/statik/fs",
|
||||
"Comment": "v0.1.0",
|
||||
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Comment": "v1.1.4-25-g2402e8e",
|
||||
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/xtaci/kcp-go",
|
||||
"Comment": "v3.17",
|
||||
"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/xtaci/smux",
|
||||
"Comment": "v1.0.5-8-g2de5471",
|
||||
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/cast5",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/salsa20",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/tea",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/twofish",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/xtea",
|
||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/bpf",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/iana",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/socket",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/ipv4",
|
||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
Godeps/Readme
generated
@@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
77
Makefile
@@ -1,5 +1,6 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
all: fmt build
|
||||
|
||||
@@ -7,47 +8,61 @@ build: frps frpc
|
||||
|
||||
# compile assets into binary file
|
||||
file:
|
||||
rm -rf ./assets/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/static
|
||||
go get -d github.com/rakyll/statik
|
||||
go install github.com/rakyll/statik
|
||||
rm -rf ./assets/statik
|
||||
go generate ./assets/...
|
||||
rm -rf ./assets/frps/static/*
|
||||
rm -rf ./assets/frpc/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||
|
||||
fmt:
|
||||
go fmt ./assets/...
|
||||
go fmt ./client/...
|
||||
go fmt ./cmd/...
|
||||
go fmt ./models/...
|
||||
go fmt ./server/...
|
||||
go fmt ./utils/...
|
||||
|
||||
go fmt ./...
|
||||
|
||||
fmt-more:
|
||||
gofumpt -l -w .
|
||||
|
||||
gci:
|
||||
gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
frps:
|
||||
go build -o bin/frps ./cmd/frps
|
||||
@cp -rf ./assets/static ./bin
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
frpc:
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
|
||||
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
go test -v ./assets/...
|
||||
go test -v ./client/...
|
||||
go test -v ./cmd/...
|
||||
go test -v ./models/...
|
||||
go test -v ./server/...
|
||||
go test -v ./utils/...
|
||||
go test -v --cover ./assets/...
|
||||
go test -v --cover ./cmd/...
|
||||
go test -v --cover ./client/...
|
||||
go test -v --cover ./server/...
|
||||
go test -v --cover ./pkg/...
|
||||
|
||||
alltest: gotest
|
||||
cd ./tests && ./run_test.sh && cd -
|
||||
go test -v ./tests/...
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
e2e:
|
||||
./hack/run-e2e.sh
|
||||
|
||||
e2e-trace:
|
||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||
|
||||
e2e-compatibility-last-frpc:
|
||||
if [ ! -d "./lastversion" ]; then \
|
||||
TARGET_DIRNAME=lastversion ./hack/download.sh; \
|
||||
fi
|
||||
FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh
|
||||
rm -r ./lastversion
|
||||
|
||||
e2e-compatibility-last-frps:
|
||||
if [ ! -d "./lastversion" ]; then \
|
||||
TARGET_DIRNAME=lastversion ./hack/download.sh; \
|
||||
fi
|
||||
FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh
|
||||
rm -r ./lastversion
|
||||
|
||||
alltest: vet gotest e2e
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
rm -f ./bin/frps
|
||||
cd ./tests && ./clean_test.sh && cd -
|
||||
|
||||
save:
|
||||
godep save ./...
|
||||
rm -rf ./lastversion
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
||||
|
||||
all: build
|
||||
|
||||
build: app
|
||||
|
||||
app:
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||
|
||||
temp:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||
@$(foreach n, $(os-archs),\
|
||||
os=$(shell echo "$(n)" | cut -d : -f 1);\
|
||||
arch=$(shell echo "$(n)" | cut -d : -f 2);\
|
||||
gomips=$(shell echo "$(n)" | cut -d : -f 3);\
|
||||
target_suffix=$${os}_$${arch};\
|
||||
echo "Build $${os}-$${arch}...";\
|
||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\
|
||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
|
||||
echo "Build $${os}-$${arch} done";\
|
||||
)
|
||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
||||
@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
|
||||
@mv ./release/frps_windows_arm64 ./release/frps_windows_arm64.exe
|
||||
|
||||
548
README_zh.md
@@ -1,519 +1,61 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
[](https://circleci.com/gh/fatedier/frp)
|
||||
[](https://github.com/fatedier/frp/releases)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。
|
||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||
|
||||
## 目录
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
* [frp 的作用](#frp-的作用)
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||
* [功能说明](#功能说明)
|
||||
* [Dashboard](#dashboard)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [服务器端热加载配置文件](#服务器端热加载配置文件)
|
||||
* [特权模式](#特权模式)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [TCP 多路复用](#tcp-多路复用)
|
||||
* [支持 kcp 协议](#支持-kcp-协议)
|
||||
* [连接池](#连接池)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
* [通过代理连接 frps](#通过代理连接-frps)
|
||||
* [插件](#插件)
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
* [捐助](#捐助)
|
||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||
* [微信支付捐赠](#微信支付捐赠)
|
||||
* [Paypal 捐赠](#paypal-捐赠)
|
||||
## 为什么使用 frp ?
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||
|
||||
## frp 的作用
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
|
||||
* 对于 http, https 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
|
||||
* 客户端服务端通信支持 TCP、QUIC、KCP 以及 Websocket 等多种协议。
|
||||
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间,降低请求延迟。
|
||||
* 代理组间的负载均衡。
|
||||
* 端口复用,多个服务通过同一个服务端端口暴露。
|
||||
* 支持 P2P 通信,流量不经过服务器中转,充分利用带宽资源。
|
||||
* 多个原生支持的客户端插件(静态文件查看,HTTPS/HTTP 协议转换,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
||||
* 高度扩展性的服务端插件系统,易于结合自身需求进行功能扩展。
|
||||
* 服务端和客户端 UI 页面。
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境。
|
||||
frp 目前已被很多公司广泛用于测试、生产环境。
|
||||
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
**目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。**
|
||||
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续较长的一段时间。
|
||||
|
||||
## 架构
|
||||
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
||||
|
||||

|
||||
### 关于 v2 的一些说明
|
||||
|
||||
## 使用示例
|
||||
当前整体形势不佳,面临的生活工作压力很大。
|
||||
|
||||
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
|
||||
v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构。
|
||||
|
||||
将 **frps** 及 **frps.ini** 放到具有公网 IP 的机器上。
|
||||
v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceMesh 方面的工作经验和思考。它的核心是一个现代化的四层和七层代理,类似于 envoy。这个代理本身高度可扩展,不仅可以用于实现内网穿透的功能,还可以应用于更多领域。在这个高度可扩展的内核基础上,我们将实现 frp v1 中的所有功能,并且能够以一种更加优雅的方式实现原先架构中无法实现或不易实现的功能。同时,我们将保持高效的开发和迭代能力。
|
||||
|
||||
将 **frpc** 及 **frpc.ini** 放到处于内网环境的机器上。
|
||||
除此之外,我希望 frp 本身也成为一个高度可扩展的系统和平台,就像我们可以基于 K8s 提供一系列扩展能力一样。在 K8s 上,我们可以根据企业需求进行定制化开发,例如使用 CRD、controller 模式、webhook、CSI 和 CNI 等。在 frp v1 中,我们引入了服务端插件的概念,实现了一些简单的扩展性。但是,它实际上依赖于简单的 HTTP 协议,并且需要用户自己启动独立的进程和管理。这种方式远远不够灵活和方便,而且现实世界的需求千差万别,我们不能期望一个由少数人维护的非营利性开源项目能够满足所有人的需求。
|
||||
|
||||
### 通过 ssh 访问公司内网机器
|
||||
最后,我们意识到像配置管理、权限验证、证书管理和管理 API 等模块的当前设计并不够现代化。尽管我们可能在 v1 版本中进行一些优化,但确保兼容性是一个令人头疼的问题,需要投入大量精力来解决。
|
||||
|
||||
1. 修改 frps.ini 文件,这里使用了最简化的配置:
|
||||
非常感谢您对 frp 的支持。
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
## 文档
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x;
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### 通过自定义域名访问部署于内网的 web 服务
|
||||
|
||||
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。
|
||||
|
||||
1. 修改 frps.ini 文件,设置 http 访问端口为 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
vhost_http_port = 8080
|
||||
```
|
||||
|
||||
2. 启动 frps;
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,假设 frps 所在的服务器的 IP 为 x.x.x.x,local_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = www.yourdomain.com
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
|
||||
|
||||
6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。
|
||||
|
||||
### 转发 DNS 查询请求
|
||||
|
||||
DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
|
||||
|
||||
1. 修改 frps.ini 文件:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x,转发到 Google 的 DNS 查询服务器 `8.8.8.8` 的 udp 53 端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 8.8.8.8
|
||||
local_port = 53
|
||||
remote_port = 6000
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
||||
|
||||
### 转发 Unix域套接字
|
||||
|
||||
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
|
||||
|
||||
1. 修改 frps.ini 文件:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
```
|
||||
|
||||
2. 启动 frps:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. 修改 frpc.ini 文件,启用 unix_domain_socket 插件:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 curl 命令查看 docker 版本信息
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### 通过 frpc 所在机器访问外网
|
||||
|
||||
frpc 内置了 http proxy 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
||||
|
||||
frps 的部署步骤同上。
|
||||
|
||||
1. 修改 frpc.ini 文件,启用 http_proxy 插件:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 浏览器设置 http 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
|
||||
|
||||
## 功能说明
|
||||
|
||||
### Dashboard
|
||||
|
||||
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
||||
|
||||
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
# dashboard 用户名密码,默认都为 admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面,用户名密码默认为 `admin`。
|
||||
|
||||

|
||||
|
||||
### 身份验证
|
||||
|
||||
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
|
||||
|
||||
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
|
||||
|
||||
这个超时时间可以在配置文件中通过 `authentication_timeout` 这个参数来修改,单位为秒,默认值为 900,即 15 分钟。如果修改为 0,则 frps 将不对身份验证报文的时间戳进行超时校验。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||
|
||||
### 服务器端热加载配置文件
|
||||
|
||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,这个功能暂时移除。
|
||||
|
||||
### 特权模式
|
||||
|
||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。
|
||||
|
||||
#### 端口白名单
|
||||
|
||||
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
```
|
||||
|
||||
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
|
||||
### TCP 多路复用
|
||||
|
||||
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
|
||||
|
||||
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
|
||||
|
||||
```ini
|
||||
# frps.ini 和 frpc.ini 中
|
||||
[common]
|
||||
tcp_mux = false
|
||||
```
|
||||
|
||||
### 支持 kcp 协议
|
||||
|
||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
|
||||
开启 kcp 协议支持:
|
||||
|
||||
1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
|
||||
kcp_bind_port = 7000
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
# server_port 指定为 frps 的 kcp_bind_port
|
||||
server_port = 7000
|
||||
protocol = kcp
|
||||
```
|
||||
|
||||
3. 像之前一样使用 frp,需要注意开放相关机器上的 udp 的端口的访问权限。
|
||||
|
||||
### 连接池
|
||||
|
||||
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
||||
|
||||
这一功能比较适合有大量短连接请求时开启。
|
||||
|
||||
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 5
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
pool_count = 1
|
||||
```
|
||||
|
||||
### 修改 Host Header
|
||||
|
||||
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
||||
|
||||
frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。
|
||||
|
||||
该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
http_user = abc
|
||||
http_pwd = abc
|
||||
```
|
||||
|
||||
通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
|
||||
|
||||
### 自定义二级域名
|
||||
|
||||
在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。
|
||||
|
||||
通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。
|
||||
|
||||
只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
subdomain_host = frps.com
|
||||
```
|
||||
|
||||
将泛域名 `*.frps.com` 解析到 frps 所在服务器的 IP 地址。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
subdomain = test
|
||||
```
|
||||
|
||||
frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
|
||||
|
||||
需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。
|
||||
|
||||
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
|
||||
|
||||
### URL 路由
|
||||
|
||||
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
|
||||
|
||||
通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_port = 81
|
||||
custom_domains = web.yourdomain.com
|
||||
locations = /news,/about
|
||||
```
|
||||
|
||||
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。
|
||||
|
||||
### 通过代理连接 frps
|
||||
|
||||
在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。
|
||||
|
||||
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
|
||||
|
||||
仅在 `protocol = tcp` 时生效。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
```
|
||||
|
||||
### 插件
|
||||
|
||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||
|
||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**。具体使用方式请查看[使用示例](#使用示例)。
|
||||
|
||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port 不再需要配置。
|
||||
|
||||
使用 **http_proxy** 插件的示例:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6000
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
||||
|
||||
## 开发计划
|
||||
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* frps 记录 http 请求日志。
|
||||
* frps 支持直接反向代理,类似 haproxy。
|
||||
* frpc 支持负载均衡到后端不同服务。
|
||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
||||
* 集成对 k8s 等平台的支持。
|
||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
@@ -524,24 +66,30 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
|
||||
* 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
|
||||
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
|
||||
* 如果你有任何其他方面的问题,欢迎反馈至 fatedier@gmail.com 共同交流。
|
||||
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
|
||||
|
||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||
|
||||
## 捐助
|
||||
## 赞助
|
||||
|
||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||
|
||||
frp 交流群:606194980 (QQ 群号)
|
||||
### Sponsors
|
||||
|
||||
### 支付宝扫码捐赠
|
||||
长期赞助可以帮助我们保持项目的持续发展。
|
||||
|
||||

|
||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||
|
||||
国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
|
||||
|
||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||
|
||||
### 知识星球
|
||||
|
||||
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
|
||||

|
||||
|
||||
### 微信支付捐赠
|
||||
|
||||

|
||||
|
||||
### Paypal 捐赠
|
||||
|
||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||
|
||||
6
Release.md
Normal file
@@ -0,0 +1,6 @@
|
||||
### Fixes
|
||||
|
||||
* `transport.tls.disableCustomTLSFirstByte` doesn't have any effect.
|
||||
* The Server API did not return the data correctly.
|
||||
* The Dashboard is unable to display data.
|
||||
* `natHoleStunServer` is missing a default value.
|
||||
@@ -14,22 +14,15 @@
|
||||
|
||||
package assets
|
||||
|
||||
//go:generate statik -src=./static
|
||||
//go:generate go fmt statik/statik.go
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
||||
// store static files in memory by statik
|
||||
// read-only filesystem created by "embed" for embedded files
|
||||
content fs.FS
|
||||
|
||||
FileSystem http.FileSystem
|
||||
|
||||
// if prefix is not empty, we get file content from disk
|
||||
@@ -38,38 +31,18 @@ var (
|
||||
|
||||
// if path is empty, load assets in memory
|
||||
// or set FileSystem using disk files
|
||||
func Load(path string) (err error) {
|
||||
func Load(path string) {
|
||||
prefixPath = path
|
||||
if prefixPath != "" {
|
||||
FileSystem = http.Dir(prefixPath)
|
||||
return nil
|
||||
} else {
|
||||
FileSystem, err = fs.New()
|
||||
FileSystem = http.FS(content)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadFile(file string) (content string, err error) {
|
||||
if prefixPath == "" {
|
||||
file, err := FileSystem.Open(path.Join("/", file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
} else {
|
||||
file, err := os.Open(path.Join(prefixPath, file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
func Register(fileSystem fs.FS) {
|
||||
subFs, err := fs.Sub(fileSystem, "static")
|
||||
if err == nil {
|
||||
content = subFs
|
||||
}
|
||||
return content, err
|
||||
}
|
||||
|
||||
14
assets/frpc/embed.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package frpc
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var content embed.FS
|
||||
|
||||
func init() {
|
||||
assets.Register(content)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
32
assets/frpc/static/index-1c7ed8b0.js
Normal file
1
assets/frpc/static/index-1e2a7ce0.css
Normal file
16
assets/frpc/static/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frp client admin UI</title>
|
||||
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
|
||||
<link rel="stylesheet" href="./index-1e2a7ce0.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
14
assets/frps/embed.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package frpc
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var content embed.FS
|
||||
|
||||
func init() {
|
||||
assets.Register(content)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frps/static/index-1e0c7400.css
Normal file
74
assets/frps/static/index-9465253b.js
Normal file
16
assets/frps/static/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frps dashboard</title>
|
||||
<script type="module" crossorigin src="./index-9465253b.js"></script>
|
||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5217927b66cc446ebfd3"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?bf962cded96400bef9a0"></script></body> </html>
|
||||
@@ -1 +0,0 @@
|
||||
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"bf962cded96400bef9a0",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);
|
||||
85
client/admin.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/healthz", svr.healthz)
|
||||
|
||||
// debug
|
||||
if svr.cfg.WebServer.PprofEnable {
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
|
||||
subRouter := router.NewRoute().Subrouter()
|
||||
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
|
||||
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
|
||||
// api, see admin_api.go
|
||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
|
||||
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
if address == "" {
|
||||
address = ":http"
|
||||
}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = server.Serve(ln)
|
||||
}()
|
||||
return
|
||||
}
|
||||
227
client/admin_api.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// GET /api/reload
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, _ *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("api request [/api/reload]")
|
||||
defer func() {
|
||||
log.Info("api response [/api/reload], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
if _, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs); err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if err := svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = err.Error()
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
log.Info("success reload conf")
|
||||
}
|
||||
|
||||
// POST /api/stop
|
||||
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("api request [/api/stop]")
|
||||
defer func() {
|
||||
log.Info("api response [/api/stop], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
go svr.GracefulClose(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
type StatusResp map[string][]ProxyStatusResp
|
||||
|
||||
type ProxyStatusResp struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
LocalAddr string `json:"local_addr"`
|
||||
Plugin string `json:"plugin"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
Type: status.Type,
|
||||
Status: status.Phase,
|
||||
Err: status.Err,
|
||||
}
|
||||
baseCfg := status.Cfg.GetBaseConfig()
|
||||
if baseCfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = baseCfg.Plugin.Type
|
||||
|
||||
if status.Err == "" {
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
if lo.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||
}
|
||||
}
|
||||
return psr
|
||||
}
|
||||
|
||||
// GET /api/status
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||
)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/status]")
|
||||
buf, _ = json.Marshal(&res)
|
||||
_, _ = w.Write(buf)
|
||||
}()
|
||||
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
|
||||
for _, arrs := range res {
|
||||
if len(arrs) <= 1 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(arrs, func(i, j int) bool {
|
||||
return strings.Compare(arrs[i].Name, arrs[j].Name) < 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/config
|
||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http get request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
if svr.cfgFile == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "frpc has no config file path"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
res.Msg = string(content)
|
||||
}
|
||||
|
||||
// PUT /api/config
|
||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http put request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
// get new config content
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
res.Code = 400
|
||||
res.Msg = "body can't be empty"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile(svr.cfgFile, body, 0o644); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -15,44 +15,45 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/utils/crypto"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/net"
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
"github.com/samber/lo"
|
||||
|
||||
const (
|
||||
connReadTimeout time.Duration = 10 * time.Second
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/client/visitor"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type Control struct {
|
||||
// frpc service
|
||||
svr *Service
|
||||
// service context
|
||||
ctx context.Context
|
||||
xl *xlog.Logger
|
||||
|
||||
// login message to server
|
||||
loginMsg *msg.Login
|
||||
// Unique ID obtained from frps.
|
||||
// It should be attached to the login message when reconnecting.
|
||||
runID string
|
||||
|
||||
// proxy configures
|
||||
pxyCfgs map[string]config.ProxyConf
|
||||
// manage all proxies
|
||||
pxyCfgs []v1.ProxyConfigurer
|
||||
pm *proxy.Manager
|
||||
|
||||
// proxies
|
||||
proxies map[string]Proxy
|
||||
// manage all visitors
|
||||
vm *visitor.Manager
|
||||
|
||||
// control connection
|
||||
conn net.Conn
|
||||
|
||||
// tcp stream multiplexing, if enabled
|
||||
session *smux.Session
|
||||
cm *ConnectionManager
|
||||
|
||||
// put a message in this channel to send it over control connection to server
|
||||
sendCh chan (msg.Message)
|
||||
@@ -60,272 +61,250 @@ type Control struct {
|
||||
// read from this channel to get the next message sent by server
|
||||
readCh chan (msg.Message)
|
||||
|
||||
// run id got from server
|
||||
runId string
|
||||
|
||||
// connection or other error happens , control will try to reconnect to server
|
||||
closed int32
|
||||
|
||||
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
||||
closedCh chan int
|
||||
closedCh chan struct{}
|
||||
|
||||
closedDoneCh chan struct{}
|
||||
|
||||
// last time got the Pong message
|
||||
lastPong time.Time
|
||||
|
||||
mu sync.RWMutex
|
||||
// The client configuration
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
|
||||
log.Logger
|
||||
readerShutdown *shutdown.Shutdown
|
||||
writerShutdown *shutdown.Shutdown
|
||||
msgHandlerShutdown *shutdown.Shutdown
|
||||
|
||||
// sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
msgTransporter transport.MessageTransporter
|
||||
}
|
||||
|
||||
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: config.ClientCommonCfg.PoolCount,
|
||||
User: config.ClientCommonCfg.User,
|
||||
Version: version.Full(),
|
||||
}
|
||||
return &Control{
|
||||
svr: svr,
|
||||
loginMsg: loginMsg,
|
||||
pxyCfgs: pxyCfgs,
|
||||
proxies: make(map[string]Proxy),
|
||||
sendCh: make(chan msg.Message, 10),
|
||||
readCh: make(chan msg.Message, 10),
|
||||
closedCh: make(chan int),
|
||||
Logger: log.NewPrefixLogger(""),
|
||||
func NewControl(
|
||||
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
authSetter auth.Setter,
|
||||
) *Control {
|
||||
// new xlog instance
|
||||
ctl := &Control{
|
||||
ctx: ctx,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
runID: runID,
|
||||
conn: conn,
|
||||
cm: cm,
|
||||
pxyCfgs: pxyCfgs,
|
||||
sendCh: make(chan msg.Message, 100),
|
||||
readCh: make(chan msg.Message, 100),
|
||||
closedCh: make(chan struct{}),
|
||||
closedDoneCh: make(chan struct{}),
|
||||
clientCfg: clientCfg,
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
authSetter: authSetter,
|
||||
}
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
|
||||
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, ctl.runID, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
return ctl
|
||||
}
|
||||
|
||||
// 1. login
|
||||
// 2. start reader() writer() manager()
|
||||
// 3. connection closed
|
||||
// 4. In reader(): close closedCh and exit, controler() get it
|
||||
// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
|
||||
// 6. In controler(): ini readCh, sendCh, closedCh
|
||||
// 7. In controler(): start new reader(), writer(), manager()
|
||||
// controler() will keep running
|
||||
func (ctl *Control) Run() error {
|
||||
for {
|
||||
err := ctl.login()
|
||||
if err != nil {
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and continues relogin to server
|
||||
if config.ClientCommonCfg.LoginFailExit {
|
||||
return err
|
||||
} else {
|
||||
ctl.Warn("login to server fail: %v", err)
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
func (ctl *Control) Run() {
|
||||
go ctl.worker()
|
||||
|
||||
go ctl.controler()
|
||||
go ctl.manager()
|
||||
go ctl.writer()
|
||||
go ctl.reader()
|
||||
// start all proxies
|
||||
ctl.pm.Reload(ctl.pxyCfgs)
|
||||
|
||||
// send NewProxy message for all configured proxies
|
||||
for _, cfg := range ctl.pxyCfgs {
|
||||
var newProxyMsg msg.NewProxy
|
||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||
ctl.sendCh <- &newProxyMsg
|
||||
}
|
||||
return nil
|
||||
// start all visitors
|
||||
go ctl.vm.Run()
|
||||
}
|
||||
|
||||
func (ctl *Control) NewWorkConn() {
|
||||
var (
|
||||
workConn net.Conn
|
||||
err error
|
||||
)
|
||||
if config.ClientCommonCfg.TcpMux {
|
||||
stream, err := ctl.session.OpenStream()
|
||||
if err != nil {
|
||||
ctl.Warn("start new work connection error: %v", err)
|
||||
return
|
||||
}
|
||||
workConn = net.WrapConn(stream)
|
||||
|
||||
} else {
|
||||
workConn, err = net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||
if err != nil {
|
||||
ctl.Warn("start new work connection error: %v", err)
|
||||
return
|
||||
}
|
||||
func (ctl *Control) HandleReqWorkConn(_ *msg.ReqWorkConn) {
|
||||
xl := ctl.xl
|
||||
workConn, err := ctl.connectServer()
|
||||
if err != nil {
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
m := &msg.NewWorkConn{
|
||||
RunId: ctl.runId,
|
||||
RunID: ctl.runID,
|
||||
}
|
||||
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
|
||||
xl.Warn("error during NewWorkConn authentication: %v", err)
|
||||
return
|
||||
}
|
||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||
ctl.Warn("work connection write to server error: %v", err)
|
||||
xl.Warn("work connection write to server error: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
var startMsg msg.StartWorkConn
|
||||
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
|
||||
ctl.Error("work connection closed, %v", err)
|
||||
xl.Trace("work connection closed before response StartWorkConn message: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
if startMsg.Error != "" {
|
||||
xl.Error("StartWorkConn contains error: %s", startMsg.Error)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||
|
||||
// dispatch this work connection to related proxy
|
||||
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
|
||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||
}
|
||||
|
||||
func (ctl *Control) init() {
|
||||
ctl.sendCh = make(chan msg.Message, 10)
|
||||
ctl.readCh = make(chan msg.Message, 10)
|
||||
ctl.closedCh = make(chan int)
|
||||
}
|
||||
|
||||
// login send a login message to server and wait for a loginResp message.
|
||||
func (ctl *Control) login() (err error) {
|
||||
if ctl.conn != nil {
|
||||
ctl.conn.Close()
|
||||
}
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
|
||||
conn, err := net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
xl := ctl.xl
|
||||
// Server will return NewProxyResp message to each NewProxy message.
|
||||
// Start a new proxy handler if no error got
|
||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||
if err != nil {
|
||||
return err
|
||||
xl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
||||
} else {
|
||||
xl.Info("[%s] start proxy success", inMsg.ProxyName)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
func (ctl *Control) HandleNatHoleResp(inMsg *msg.NatHoleResp) {
|
||||
xl := ctl.xl
|
||||
|
||||
if config.ClientCommonCfg.TcpMux {
|
||||
session, errRet := smux.Client(conn, nil)
|
||||
if errRet != nil {
|
||||
return errRet
|
||||
}
|
||||
stream, errRet := session.OpenStream()
|
||||
if errRet != nil {
|
||||
session.Close()
|
||||
return errRet
|
||||
}
|
||||
conn = net.WrapConn(stream)
|
||||
ctl.session = session
|
||||
// Dispatch the NatHoleResp message to the related proxy.
|
||||
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||
if !ok {
|
||||
xl.Trace("dispatch NatHoleResp message to related proxy error")
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
|
||||
ctl.loginMsg.Timestamp = now
|
||||
ctl.loginMsg.RunId = ctl.runId
|
||||
func (ctl *Control) Close() error {
|
||||
return ctl.GracefulClose(0)
|
||||
}
|
||||
|
||||
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
func (ctl *Control) GracefulClose(d time.Duration) error {
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
ctl.Error("%s", loginRespMsg.Error)
|
||||
return err
|
||||
}
|
||||
|
||||
ctl.conn = conn
|
||||
// update runId got from server
|
||||
ctl.runId = loginRespMsg.RunId
|
||||
ctl.ClearLogPrefix()
|
||||
ctl.AddLogPrefix(loginRespMsg.RunId)
|
||||
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
|
||||
|
||||
// login success, so we let closedCh available again
|
||||
ctl.closedCh = make(chan int)
|
||||
ctl.lastPong = time.Now()
|
||||
time.Sleep(d)
|
||||
|
||||
ctl.conn.Close()
|
||||
ctl.cm.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosedDoneCh returns a channel that will be closed after all resources are released
|
||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
return ctl.closedDoneCh
|
||||
}
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
return ctl.cm.Connect()
|
||||
}
|
||||
|
||||
// reader read all messages from frps and send to readCh
|
||||
func (ctl *Control) reader() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.readerShutdown.Done()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||
for {
|
||||
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||
m, err := msg.ReadMsg(encReader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
ctl.Debug("read from control connection EOF")
|
||||
return
|
||||
} else {
|
||||
ctl.Warn("read error: %v", err)
|
||||
xl.Debug("read from control connection EOF")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctl.readCh <- m
|
||||
xl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.readCh <- m
|
||||
}
|
||||
}
|
||||
|
||||
// writer writes messages got from sendCh to frps
|
||||
func (ctl *Control) writer() {
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
xl := ctl.xl
|
||||
defer ctl.writerShutdown.Done()
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
ctl.conn.Error("crypto new writer error: %v", err)
|
||||
xl.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
for {
|
||||
if m, ok := <-ctl.sendCh; !ok {
|
||||
ctl.Info("control writer is closing")
|
||||
m, ok := <-ctl.sendCh
|
||||
if !ok {
|
||||
xl.Info("control writer is closing")
|
||||
return
|
||||
}
|
||||
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
xl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
} else {
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
ctl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) manager() {
|
||||
// msgHandler handles all channel events and performs corresponding operations.
|
||||
func (ctl *Control) msgHandler() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ctl.Error("panic error: %v", err)
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
var hbSendCh <-chan time.Time
|
||||
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
||||
// Just keep it here to keep compatible with old version frps.
|
||||
if ctl.clientCfg.Transport.HeartbeatInterval > 0 {
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.Transport.HeartbeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbSendCh = hbSend.C
|
||||
}
|
||||
|
||||
var hbCheckCh <-chan time.Time
|
||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||
if ctl.clientCfg.Transport.HeartbeatInterval > 0 && ctl.clientCfg.Transport.HeartbeatTimeout > 0 &&
|
||||
!lo.FromPtr(ctl.clientCfg.Transport.TCPMux) {
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
hbCheckCh = hbCheck.C
|
||||
}
|
||||
|
||||
ctl.lastPong = time.Now()
|
||||
for {
|
||||
select {
|
||||
case <-hbSend.C:
|
||||
case <-hbSendCh:
|
||||
// send heartbeat to server
|
||||
ctl.Debug("send heartbeat to server")
|
||||
ctl.sendCh <- &msg.Ping{}
|
||||
case <-hbCheck.C:
|
||||
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
|
||||
ctl.Warn("heartbeat timeout")
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.authSetter.SetPing(pingMsg); err != nil {
|
||||
xl.Warn("error during ping authentication: %v", err)
|
||||
return
|
||||
}
|
||||
ctl.sendCh <- pingMsg
|
||||
case <-hbCheckCh:
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
return
|
||||
@@ -337,110 +316,48 @@ func (ctl *Control) manager() {
|
||||
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.ReqWorkConn:
|
||||
go ctl.NewWorkConn()
|
||||
go ctl.HandleReqWorkConn(m)
|
||||
case *msg.NewProxyResp:
|
||||
// Server will return NewProxyResp message to each NewProxy message.
|
||||
// Start a new proxy handler if no error got
|
||||
if m.Error != "" {
|
||||
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
|
||||
continue
|
||||
}
|
||||
cfg, ok := ctl.pxyCfgs[m.ProxyName]
|
||||
if !ok {
|
||||
// it will never go to this branch now
|
||||
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
|
||||
continue
|
||||
}
|
||||
oldPxy, ok := ctl.proxies[m.ProxyName]
|
||||
if ok {
|
||||
oldPxy.Close()
|
||||
}
|
||||
pxy := NewProxy(ctl, cfg)
|
||||
if err := pxy.Run(); err != nil {
|
||||
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
|
||||
ctl.sendCh <- &msg.CloseProxy{
|
||||
ProxyName: m.ProxyName,
|
||||
}
|
||||
continue
|
||||
}
|
||||
ctl.proxies[m.ProxyName] = pxy
|
||||
ctl.Info("[%s] start proxy success", m.ProxyName)
|
||||
ctl.HandleNewProxyResp(m)
|
||||
case *msg.NatHoleResp:
|
||||
ctl.HandleNatHoleResp(m)
|
||||
case *msg.Pong:
|
||||
if m.Error != "" {
|
||||
xl.Error("Pong contains error: %s", m.Error)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.lastPong = time.Now()
|
||||
ctl.Debug("receive heartbeat from server")
|
||||
xl.Debug("receive heartbeat from server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// control keep watching closedCh, start a new connection if previous control connection is closed
|
||||
func (ctl *Control) controler() {
|
||||
var err error
|
||||
maxDelayTime := 30 * time.Second
|
||||
delayTime := time.Second
|
||||
// If controler is notified by closedCh, reader and writer and handler will exit
|
||||
func (ctl *Control) worker() {
|
||||
go ctl.msgHandler()
|
||||
go ctl.reader()
|
||||
go ctl.writer()
|
||||
|
||||
checkInterval := 30 * time.Second
|
||||
checkProxyTicker := time.NewTicker(checkInterval)
|
||||
for {
|
||||
select {
|
||||
case <-checkProxyTicker.C:
|
||||
// Every 30 seconds, check which proxy registered failed and reregister it to server.
|
||||
for _, cfg := range ctl.pxyCfgs {
|
||||
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
|
||||
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
|
||||
var newProxyMsg msg.NewProxy
|
||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||
ctl.sendCh <- &newProxyMsg
|
||||
}
|
||||
}
|
||||
case _, ok := <-ctl.closedCh:
|
||||
// we won't get any variable from this channel
|
||||
if !ok {
|
||||
// close related channels
|
||||
close(ctl.readCh)
|
||||
close(ctl.sendCh)
|
||||
<-ctl.closedCh
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
|
||||
for _, pxy := range ctl.proxies {
|
||||
pxy.Close()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
|
||||
// loop util reconnect to server success
|
||||
for {
|
||||
ctl.Info("try to reconnect to server...")
|
||||
err = ctl.login()
|
||||
if err != nil {
|
||||
ctl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init the delayTime
|
||||
delayTime = time.Second
|
||||
break
|
||||
}
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
|
||||
// init related channels and variables
|
||||
ctl.init()
|
||||
|
||||
// previous work goroutines should be closed and start them here
|
||||
go ctl.manager()
|
||||
go ctl.writer()
|
||||
go ctl.reader()
|
||||
|
||||
// send NewProxy message for all configured proxies
|
||||
for _, cfg := range ctl.pxyCfgs {
|
||||
var newProxyMsg msg.NewProxy
|
||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||
ctl.sendCh <- &newProxyMsg
|
||||
}
|
||||
|
||||
checkProxyTicker.Stop()
|
||||
checkProxyTicker = time.NewTicker(checkInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
close(ctl.closedDoneCh)
|
||||
ctl.cm.Close()
|
||||
}
|
||||
|
||||
func (ctl *Control) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
ctl.pm.Reload(pxyCfgs)
|
||||
return nil
|
||||
}
|
||||
|
||||
19
client/event/event.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
var ErrPayloadType = errors.New("error payload type")
|
||||
|
||||
type Handler func(payload interface{}) error
|
||||
|
||||
type StartProxyPayload struct {
|
||||
NewProxyMsg *msg.NewProxy
|
||||
}
|
||||
|
||||
type CloseProxyPayload struct {
|
||||
CloseProxyMsg *msg.CloseProxy
|
||||
}
|
||||
177
client/health/health.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var ErrHealthCheckType = errors.New("error health check type")
|
||||
|
||||
type Monitor struct {
|
||||
checkType string
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
maxFailedTimes int
|
||||
|
||||
// For tcp
|
||||
addr string
|
||||
|
||||
// For http
|
||||
url string
|
||||
|
||||
failedTimes uint64
|
||||
statusOK bool
|
||||
statusNormalFn func()
|
||||
statusFailedFn func()
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
|
||||
statusNormalFn func(), statusFailedFn func(),
|
||||
) *Monitor {
|
||||
if cfg.IntervalSeconds <= 0 {
|
||||
cfg.IntervalSeconds = 10
|
||||
}
|
||||
if cfg.TimeoutSeconds <= 0 {
|
||||
cfg.TimeoutSeconds = 3
|
||||
}
|
||||
if cfg.MaxFailed <= 0 {
|
||||
cfg.MaxFailed = 1
|
||||
}
|
||||
newctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
var url string
|
||||
if cfg.Type == "http" && cfg.Path != "" {
|
||||
s := "http://" + addr
|
||||
if !strings.HasPrefix(cfg.Path, "/") {
|
||||
s += "/"
|
||||
}
|
||||
url = s + cfg.Path
|
||||
}
|
||||
return &Monitor{
|
||||
checkType: cfg.Type,
|
||||
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
|
||||
timeout: time.Duration(cfg.TimeoutSeconds) * time.Second,
|
||||
maxFailedTimes: cfg.MaxFailed,
|
||||
addr: addr,
|
||||
url: url,
|
||||
statusOK: false,
|
||||
statusNormalFn: statusNormalFn,
|
||||
statusFailedFn: statusFailedFn,
|
||||
ctx: newctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *Monitor) Start() {
|
||||
go monitor.checkWorker()
|
||||
}
|
||||
|
||||
func (monitor *Monitor) Stop() {
|
||||
monitor.cancel()
|
||||
}
|
||||
|
||||
func (monitor *Monitor) checkWorker() {
|
||||
xl := xlog.FromContextSafe(monitor.ctx)
|
||||
for {
|
||||
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||
err := monitor.doCheck(doCtx)
|
||||
|
||||
// check if this monitor has been closed
|
||||
select {
|
||||
case <-monitor.ctx.Done():
|
||||
cancel()
|
||||
return
|
||||
default:
|
||||
cancel()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
xl.Trace("do one health check success")
|
||||
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
||||
xl.Info("health check status change to success")
|
||||
monitor.statusOK = true
|
||||
monitor.statusNormalFn()
|
||||
}
|
||||
} else {
|
||||
xl.Warn("do one health check failed: %v", err)
|
||||
monitor.failedTimes++
|
||||
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
||||
xl.Warn("health check status change to failed")
|
||||
monitor.statusOK = false
|
||||
monitor.statusFailedFn()
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(monitor.interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *Monitor) doCheck(ctx context.Context) error {
|
||||
switch monitor.checkType {
|
||||
case "tcp":
|
||||
return monitor.doTCPCheck(ctx)
|
||||
case "http":
|
||||
return monitor.doHTTPCheck(ctx)
|
||||
default:
|
||||
return ErrHealthCheckType
|
||||
}
|
||||
}
|
||||
|
||||
func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
|
||||
// if tcp address is not specified, always return nil
|
||||
if monitor.addr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", monitor.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", monitor.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
308
client/proxy.go
@@ -1,308 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
"github.com/fatedier/frp/models/plugin"
|
||||
"github.com/fatedier/frp/models/proto/udp"
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
frpIo "github.com/fatedier/frp/utils/io"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
// Proxy defines how to work for different proxy type.
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(conn frpNet.Conn)
|
||||
Close()
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
baseProxy := BaseProxy{
|
||||
ctl: ctl,
|
||||
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
ctl *Control
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
log.Logger
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Run() (err error) {
|
||||
if pxy.cfg.Plugin != "" {
|
||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UdpPacket
|
||||
|
||||
// include msg.UdpPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn frpNet.Conn
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
if !pxy.closed {
|
||||
pxy.closed = true
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
if pxy.readCh != nil {
|
||||
close(pxy.readCh)
|
||||
}
|
||||
if pxy.sendCh != nil {
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
|
||||
for {
|
||||
var udpMsg msg.UdpPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
pxy.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UdpPacket:
|
||||
pxy.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
pxy.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
pxy.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
var errRet error
|
||||
for {
|
||||
time.Sleep(time.Duration(30) * time.Second)
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
pxy.Trace("heartbeat goroutine for udp work connection closed")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.workConn, pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
|
||||
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
err error
|
||||
)
|
||||
remote = workConn
|
||||
if baseInfo.UseEncryption {
|
||||
remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
|
||||
if err != nil {
|
||||
workConn.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if baseInfo.UseCompression {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
if proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connections first
|
||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||
if err != nil {
|
||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
frpIo.Join(localConn, remote)
|
||||
workConn.Debug("join connections closed")
|
||||
}
|
||||
}
|
||||
47
client/proxy/general_tcp.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pxyConfs := []v1.ProxyConfigurer{
|
||||
&v1.TCPProxyConfig{},
|
||||
&v1.HTTPProxyConfig{},
|
||||
&v1.HTTPSProxyConfig{},
|
||||
&v1.STCPProxyConfig{},
|
||||
&v1.TCPMuxProxyConfig{},
|
||||
}
|
||||
for _, cfg := range pxyConfs {
|
||||
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
|
||||
}
|
||||
}
|
||||
|
||||
// GeneralTCPProxy is a general implementation of Proxy interface for TCP protocol.
|
||||
// If the default GeneralTCPProxy cannot meet the requirements, you can customize
|
||||
// the implementation of the Proxy interface.
|
||||
type GeneralTCPProxy struct {
|
||||
*BaseProxy
|
||||
}
|
||||
|
||||
func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {
|
||||
return &GeneralTCPProxy{
|
||||
BaseProxy: baseProxy,
|
||||
}
|
||||
}
|
||||
218
client/proxy/proxy.go
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
||||
|
||||
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v1.ProxyConfigurer) Proxy) {
|
||||
proxyFactoryRegistry[proxyConfType] = factory
|
||||
}
|
||||
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(net.Conn, *msg.StartWorkConn)
|
||||
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewProxy(
|
||||
ctx context.Context,
|
||||
pxyConf v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) (pxy Proxy) {
|
||||
var limiter *rate.Limiter
|
||||
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
||||
if limitBytes > 0 && pxyConf.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeClient {
|
||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||
}
|
||||
|
||||
baseProxy := BaseProxy{
|
||||
baseCfg: pxyConf.GetBaseConfig(),
|
||||
clientCfg: clientCfg,
|
||||
limiter: limiter,
|
||||
msgTransporter: msgTransporter,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
|
||||
if factory == nil {
|
||||
return nil
|
||||
}
|
||||
return factory(&baseProxy, pxyConf)
|
||||
}
|
||||
|
||||
type BaseProxy struct {
|
||||
baseCfg *v1.ProxyBaseConfig
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
msgTransporter transport.MessageTransporter
|
||||
limiter *rate.Limiter
|
||||
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||
// It's only validate for TCP protocol now.
|
||||
proxyPlugin plugin.Plugin
|
||||
|
||||
mu sync.RWMutex
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) Run() error {
|
||||
if pxy.baseCfg.Plugin.Type != "" {
|
||||
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pxy.proxyPlugin = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) Close() {
|
||||
if pxy.proxyPlugin != nil {
|
||||
pxy.proxyPlugin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
|
||||
xl := pxy.xl
|
||||
baseCfg := pxy.baseCfg
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
err error
|
||||
)
|
||||
remote = workConn
|
||||
if pxy.limiter != nil {
|
||||
remote = libio.WrapReadWriteCloser(limit.NewReader(workConn, pxy.limiter), limit.NewWriter(workConn, pxy.limiter), func() error {
|
||||
return workConn.Close()
|
||||
})
|
||||
}
|
||||
|
||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
||||
if baseCfg.Transport.UseEncryption {
|
||||
remote, err = libio.WithEncryption(remote, encKey)
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var compressionResourceRecycleFn func()
|
||||
if baseCfg.Transport.UseCompression {
|
||||
remote, compressionResourceRecycleFn = libio.WithCompressionFromPool(remote)
|
||||
}
|
||||
|
||||
// check if we need to send proxy protocol info
|
||||
var extraInfo plugin.ExtraInfo
|
||||
if baseCfg.Transport.ProxyProtocolVersion != "" {
|
||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
if m.DstAddr == "" {
|
||||
m.DstAddr = "127.0.0.1"
|
||||
}
|
||||
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
|
||||
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddr: srcAddr,
|
||||
DestinationAddr: dstAddr,
|
||||
}
|
||||
|
||||
if strings.Contains(m.SrcAddr, ".") {
|
||||
h.TransportProtocol = pp.TCPv4
|
||||
} else {
|
||||
h.TransportProtocol = pp.TCPv6
|
||||
}
|
||||
|
||||
if baseCfg.Transport.ProxyProtocolVersion == "v1" {
|
||||
h.Version = 1
|
||||
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
||||
h.Version = 2
|
||||
}
|
||||
|
||||
extraInfo.ProxyProtocolHeader = h
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connection first
|
||||
xl.Debug("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||
pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
|
||||
xl.Debug("handle by plugin finished")
|
||||
return
|
||||
}
|
||||
|
||||
localConn, err := libdial.Dial(
|
||||
net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)),
|
||||
libdial.WithTimeout(10*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
|
||||
if extraInfo.ProxyProtocolHeader != nil {
|
||||
if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("write proxy protocol header to local conn error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, _, errs := libio.Join(localConn, remote)
|
||||
xl.Debug("join connections closed")
|
||||
if len(errs) > 0 {
|
||||
xl.Trace("join connections errors: %v", errs)
|
||||
}
|
||||
if compressionResourceRecycleFn != nil {
|
||||
compressionResourceRecycleFn()
|
||||
}
|
||||
}
|
||||
158
client/proxy/proxy_manager.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/event"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
proxies map[string]*Wrapper
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
proxies: make(map[string]*Wrapper),
|
||||
msgTransporter: msgTransporter,
|
||||
closed: false,
|
||||
clientCfg: clientCfg,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
|
||||
pm.mu.RLock()
|
||||
pxy, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("proxy [%s] not found", name)
|
||||
}
|
||||
|
||||
err := pxy.SetRunningStatus(remoteAddr, serverRespErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *Manager) Close() {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
pxy.Stop()
|
||||
}
|
||||
pm.proxies = make(map[string]*Wrapper)
|
||||
}
|
||||
|
||||
func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *Manager) HandleEvent(payload interface{}) error {
|
||||
var m msg.Message
|
||||
switch e := payload.(type) {
|
||||
case *event.StartProxyPayload:
|
||||
m = e.NewProxyMsg
|
||||
case *event.CloseProxyPayload:
|
||||
m = e.CloseProxyMsg
|
||||
default:
|
||||
return event.ErrPayloadType
|
||||
}
|
||||
|
||||
return pm.msgTransporter.Send(m)
|
||||
}
|
||||
|
||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||
ps := make([]*WorkingStatus, 0)
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
for _, pxy := range pm.proxies {
|
||||
ps = append(ps, pxy.GetStatus())
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
func (pm *Manager) Reload(pxyCfgs []v1.ProxyConfigurer) {
|
||||
xl := xlog.FromContextSafe(pm.ctx)
|
||||
pxyCfgsMap := lo.KeyBy(pxyCfgs, func(c v1.ProxyConfigurer) string {
|
||||
return c.GetBaseConfig().Name
|
||||
})
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
delPxyNames := make([]string, 0)
|
||||
for name, pxy := range pm.proxies {
|
||||
del := false
|
||||
cfg, ok := pxyCfgsMap[name]
|
||||
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
||||
del = true
|
||||
}
|
||||
|
||||
if del {
|
||||
delPxyNames = append(delPxyNames, name)
|
||||
delete(pm.proxies, name)
|
||||
pxy.Stop()
|
||||
}
|
||||
}
|
||||
if len(delPxyNames) > 0 {
|
||||
xl.Info("proxy removed: %s", delPxyNames)
|
||||
}
|
||||
|
||||
addPxyNames := make([]string, 0)
|
||||
for _, cfg := range pxyCfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
pxy.Start()
|
||||
}
|
||||
}
|
||||
if len(addPxyNames) > 0 {
|
||||
xl.Info("proxy added: %s", addPxyNames)
|
||||
}
|
||||
}
|
||||
275
client/proxy/proxy_wrapper.go
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
|
||||
"github.com/fatedier/frp/client/event"
|
||||
"github.com/fatedier/frp/client/health"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
const (
|
||||
ProxyPhaseNew = "new"
|
||||
ProxyPhaseWaitStart = "wait start"
|
||||
ProxyPhaseStartErr = "start error"
|
||||
ProxyPhaseRunning = "running"
|
||||
ProxyPhaseCheckFailed = "check failed"
|
||||
ProxyPhaseClosed = "closed"
|
||||
)
|
||||
|
||||
var (
|
||||
statusCheckInterval = 3 * time.Second
|
||||
waitResponseTimeout = 20 * time.Second
|
||||
startErrTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type WorkingStatus struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Phase string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
Cfg v1.ProxyConfigurer `json:"cfg"`
|
||||
|
||||
// Got from server.
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
type Wrapper struct {
|
||||
WorkingStatus
|
||||
|
||||
// underlying proxy
|
||||
pxy Proxy
|
||||
|
||||
// if ProxyConf has healcheck config
|
||||
// monitor will watch if it is alive
|
||||
monitor *health.Monitor
|
||||
|
||||
// event handler
|
||||
handler event.Handler
|
||||
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
health uint32
|
||||
lastSendStartMsg time.Time
|
||||
lastStartErr time.Time
|
||||
closeCh chan struct{}
|
||||
healthNotifyCh chan struct{}
|
||||
mu sync.RWMutex
|
||||
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewWrapper(
|
||||
ctx context.Context,
|
||||
cfg v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
eventHandler event.Handler,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Wrapper {
|
||||
baseInfo := cfg.GetBaseConfig()
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
||||
pw := &Wrapper{
|
||||
WorkingStatus: WorkingStatus{
|
||||
Name: baseInfo.Name,
|
||||
Type: baseInfo.Type,
|
||||
Phase: ProxyPhaseNew,
|
||||
Cfg: cfg,
|
||||
},
|
||||
closeCh: make(chan struct{}),
|
||||
healthNotifyCh: make(chan struct{}),
|
||||
handler: eventHandler,
|
||||
msgTransporter: msgTransporter,
|
||||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
}
|
||||
|
||||
if baseInfo.HealthCheck.Type != "" && baseInfo.LocalPort > 0 {
|
||||
pw.health = 1 // means failed
|
||||
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
|
||||
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
|
||||
pw.statusNormalCallback, pw.statusFailedCallback)
|
||||
xl.Trace("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
|
||||
return pw
|
||||
}
|
||||
|
||||
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
if pw.Phase != ProxyPhaseWaitStart {
|
||||
return fmt.Errorf("status not wait start, ignore start message")
|
||||
}
|
||||
|
||||
pw.RemoteAddr = remoteAddr
|
||||
if respErr != "" {
|
||||
pw.Phase = ProxyPhaseStartErr
|
||||
pw.Err = respErr
|
||||
pw.lastStartErr = time.Now()
|
||||
return fmt.Errorf(pw.Err)
|
||||
}
|
||||
|
||||
if err := pw.pxy.Run(); err != nil {
|
||||
pw.close()
|
||||
pw.Phase = ProxyPhaseStartErr
|
||||
pw.Err = err.Error()
|
||||
pw.lastStartErr = time.Now()
|
||||
return err
|
||||
}
|
||||
|
||||
pw.Phase = ProxyPhaseRunning
|
||||
pw.Err = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *Wrapper) Start() {
|
||||
go pw.checkWorker()
|
||||
if pw.monitor != nil {
|
||||
go pw.monitor.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *Wrapper) Stop() {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
close(pw.closeCh)
|
||||
close(pw.healthNotifyCh)
|
||||
pw.pxy.Close()
|
||||
if pw.monitor != nil {
|
||||
pw.monitor.Stop()
|
||||
}
|
||||
pw.Phase = ProxyPhaseClosed
|
||||
pw.close()
|
||||
}
|
||||
|
||||
func (pw *Wrapper) close() {
|
||||
_ = pw.handler(&event.CloseProxyPayload{
|
||||
CloseProxyMsg: &msg.CloseProxy{
|
||||
ProxyName: pw.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (pw *Wrapper) checkWorker() {
|
||||
xl := pw.xl
|
||||
if pw.monitor != nil {
|
||||
// let monitor do check request first
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
for {
|
||||
// check proxy status
|
||||
now := time.Now()
|
||||
if atomic.LoadUint32(&pw.health) == 0 {
|
||||
pw.mu.Lock()
|
||||
if pw.Phase == ProxyPhaseNew ||
|
||||
pw.Phase == ProxyPhaseCheckFailed ||
|
||||
(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||
(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||
|
||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
|
||||
pw.Phase = ProxyPhaseWaitStart
|
||||
|
||||
var newProxyMsg msg.NewProxy
|
||||
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||
pw.lastSendStartMsg = now
|
||||
_ = pw.handler(&event.StartProxyPayload{
|
||||
NewProxyMsg: &newProxyMsg,
|
||||
})
|
||||
}
|
||||
pw.mu.Unlock()
|
||||
} else {
|
||||
pw.mu.Lock()
|
||||
if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
|
||||
pw.close()
|
||||
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
|
||||
pw.Phase = ProxyPhaseCheckFailed
|
||||
}
|
||||
pw.mu.Unlock()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-pw.closeCh:
|
||||
return
|
||||
case <-time.After(statusCheckInterval):
|
||||
case <-pw.healthNotifyCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *Wrapper) statusNormalCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 0)
|
||||
_ = errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
xl.Info("health check success")
|
||||
}
|
||||
|
||||
func (pw *Wrapper) statusFailedCallback() {
|
||||
xl := pw.xl
|
||||
atomic.StoreUint32(&pw.health, 1)
|
||||
_ = errors.PanicToError(func() {
|
||||
select {
|
||||
case pw.healthNotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
xl.Info("health check failed")
|
||||
}
|
||||
|
||||
func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
|
||||
xl := pw.xl
|
||||
pw.mu.RLock()
|
||||
pxy := pw.pxy
|
||||
pw.mu.RUnlock()
|
||||
if pxy != nil && pw.Phase == ProxyPhaseRunning {
|
||||
xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pw *Wrapper) GetStatus() *WorkingStatus {
|
||||
pw.mu.RLock()
|
||||
defer pw.mu.RUnlock()
|
||||
ps := &WorkingStatus{
|
||||
Name: pw.Name,
|
||||
Type: pw.Type,
|
||||
Phase: pw.Phase,
|
||||
Err: pw.Err,
|
||||
Cfg: pw.Cfg,
|
||||
RemoteAddr: pw.RemoteAddr,
|
||||
}
|
||||
return ps
|
||||
}
|
||||
207
client/proxy/sudp.go
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
libio "github.com/fatedier/golib/io"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
|
||||
}
|
||||
|
||||
type SUDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *v1.SUDPProxyConfig
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||
unwrapped, ok := cfg.(*v1.SUDPProxyConfig)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &SUDPProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: unwrapped,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
return
|
||||
default:
|
||||
close(pxy.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.Transport.UseCompression {
|
||||
rwc = libio.WithCompression(rwc)
|
||||
}
|
||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
workConn := conn
|
||||
readCh := make(chan *msg.UDPPacket, 1024)
|
||||
sendCh := make(chan msg.Message, 1024)
|
||||
isClose := false
|
||||
|
||||
mu := &sync.Mutex{}
|
||||
|
||||
closeFn := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
|
||||
isClose = true
|
||||
if workConn != nil {
|
||||
workConn.Close()
|
||||
}
|
||||
close(readCh)
|
||||
close(sendCh)
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
defer closeFn()
|
||||
|
||||
for {
|
||||
// first to check sudp proxy is closed or not
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
if errRet := errors.PanicToError(func() {
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
closeFn()
|
||||
xl.Info("writer goroutine for sudp work connection closed")
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
case *msg.Ping:
|
||||
xl.Trace("frpc send ping message to frpc visitor")
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("sudp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heartbeatFn := func(sendCh chan msg.Message) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
closeFn()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||
return
|
||||
}
|
||||
case <-pxy.closeCh:
|
||||
xl.Trace("frpc sudp proxy is closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(workConn, sendCh)
|
||||
go workConnReaderFn(workConn, readCh)
|
||||
go heartbeatFn(sendCh)
|
||||
|
||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
173
client/proxy/udp.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
libio "github.com/fatedier/golib/io"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
|
||||
}
|
||||
|
||||
type UDPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *v1.UDPProxyConfig
|
||||
|
||||
localAddr *net.UDPAddr
|
||||
readCh chan *msg.UDPPacket
|
||||
|
||||
// include msg.UDPPacket and msg.Ping
|
||||
sendCh chan msg.Message
|
||||
workConn net.Conn
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||
unwrapped, ok := cfg.(*v1.UDPProxyConfig)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &UDPProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: unwrapped,
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Run() (err error) {
|
||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) Close() {
|
||||
pxy.mu.Lock()
|
||||
defer pxy.mu.Unlock()
|
||||
|
||||
if !pxy.closed {
|
||||
pxy.closed = true
|
||||
if pxy.workConn != nil {
|
||||
pxy.workConn.Close()
|
||||
}
|
||||
if pxy.readCh != nil {
|
||||
close(pxy.readCh)
|
||||
}
|
||||
if pxy.sendCh != nil {
|
||||
close(pxy.sendCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
var err error
|
||||
if pxy.limiter != nil {
|
||||
rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||
return conn.Close()
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if pxy.cfg.Transport.UseCompression {
|
||||
rwc = libio.WithCompression(rwc)
|
||||
}
|
||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
pxy.sendCh = make(chan msg.Message, 1024)
|
||||
pxy.closed = false
|
||||
pxy.mu.Unlock()
|
||||
|
||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||
for {
|
||||
var udpMsg msg.UDPPacket
|
||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||
return
|
||||
}
|
||||
if errRet := errors.PanicToError(func() {
|
||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||
readCh <- &udpMsg
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||
defer func() {
|
||||
xl.Info("writer goroutine for udp work connection closed")
|
||||
}()
|
||||
var errRet error
|
||||
for rawMsg := range sendCh {
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.UDPPacket:
|
||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||
case *msg.Ping:
|
||||
xl.Trace("send ping message to udp workConn")
|
||||
}
|
||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||
xl.Error("udp work write error: %v", errRet)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
heartbeatFn := func(sendCh chan msg.Message) {
|
||||
var errRet error
|
||||
for {
|
||||
time.Sleep(time.Duration(30) * time.Second)
|
||||
if errRet = errors.PanicToError(func() {
|
||||
sendCh <- &msg.Ping{}
|
||||
}); errRet != nil {
|
||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
go heartbeatFn(pxy.sendCh)
|
||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||
}
|
||||
197
client/proxy/xtcp.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
"github.com/quic-go/quic-go"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
|
||||
}
|
||||
|
||||
type XTCPProxy struct {
|
||||
*BaseProxy
|
||||
|
||||
cfg *v1.XTCPProxyConfig
|
||||
}
|
||||
|
||||
func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||
unwrapped, ok := cfg.(*v1.XTCPProxyConfig)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &XTCPProxy{
|
||||
BaseProxy: baseProxy,
|
||||
cfg: unwrapped,
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
if err != nil {
|
||||
xl.Error("xtcp read from workConn error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Trace("nathole prepare start")
|
||||
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||
if err != nil {
|
||||
xl.Warn("nathole prepare error: %v", err)
|
||||
return
|
||||
}
|
||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||
defer prepareResult.ListenConn.Close()
|
||||
|
||||
// send NatHoleClient msg to server
|
||||
transactionID := nathole.NewTransactionID()
|
||||
natHoleClientMsg := &msg.NatHoleClient{
|
||||
TransactionID: transactionID,
|
||||
ProxyName: pxy.cfg.Name,
|
||||
Sid: natHoleSidMsg.Sid,
|
||||
MappedAddrs: prepareResult.Addrs,
|
||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||
}
|
||||
|
||||
xl.Trace("nathole exchange info start")
|
||||
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||
if err != nil {
|
||||
xl.Warn("nathole exchange info error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||
|
||||
listenConn := prepareResult.ListenConn
|
||||
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("make hole error: %v", err)
|
||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||
Sid: natHoleRespMsg.Sid,
|
||||
Success: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
listenConn = newListenConn
|
||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||
|
||||
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||
Sid: natHoleRespMsg.Sid,
|
||||
Success: true,
|
||||
})
|
||||
|
||||
if natHoleRespMsg.Protocol == "kcp" {
|
||||
pxy.listenByKCP(listenConn, raddr, startWorkConnMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// default is quic
|
||||
pxy.listenByQUIC(listenConn, raddr, startWorkConnMsg)
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
listenConn.Close()
|
||||
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||
if err != nil {
|
||||
xl.Warn("dial udp error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
xl.Warn("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Server(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
xl.Error("create mux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
for {
|
||||
muxConn, err := session.Accept()
|
||||
if err != nil {
|
||||
xl.Error("accept connection error: %v", err)
|
||||
return
|
||||
}
|
||||
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer listenConn.Close()
|
||||
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
if err != nil {
|
||||
xl.Warn("create tls config error: %v", err)
|
||||
return
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||
&quic.Config{
|
||||
MaxIdleTimeout: time.Duration(pxy.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(pxy.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(pxy.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
xl.Warn("dial quic error: %v", err)
|
||||
return
|
||||
}
|
||||
// only accept one connection from raddr
|
||||
c, err := quicListener.Accept(pxy.ctx)
|
||||
if err != nil {
|
||||
xl.Error("quic accept connection error: %v", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
stream, err := c.AcceptStream(pxy.ctx)
|
||||
if err != nil {
|
||||
xl.Debug("quic accept stream error: %v", err)
|
||||
_ = c.CloseWithError(0, "")
|
||||
return
|
||||
}
|
||||
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||
}
|
||||
}
|
||||
@@ -14,30 +14,498 @@
|
||||
|
||||
package client
|
||||
|
||||
import "github.com/fatedier/frp/models/config"
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
type Service struct {
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"github.com/samber/lo"
|
||||
|
||||
closedCh chan int
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
}
|
||||
|
||||
func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runID string
|
||||
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
ctlMu sync.RWMutex
|
||||
|
||||
// Sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
cfg *v1.ClientCommonConfig
|
||||
pxyCfgs []v1.ProxyConfigurer
|
||||
visitorCfgs []v1.VisitorConfigurer
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
// The configuration file used to initialize this client, or an empty
|
||||
// string if no configuration file was used.
|
||||
cfgFile string
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
|
||||
// service context
|
||||
ctx context.Context
|
||||
// call cancel to stop service
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewService(
|
||||
cfg *v1.ClientCommonConfig,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
cfgFile string,
|
||||
) (svr *Service, err error) {
|
||||
svr = &Service{
|
||||
closedCh: make(chan int),
|
||||
authSetter: auth.NewAuthSetter(cfg.Auth),
|
||||
cfg: cfg,
|
||||
cfgFile: cfgFile,
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
ctx: context.Background(),
|
||||
exit: 0,
|
||||
}
|
||||
ctl := NewControl(svr, pxyCfgs)
|
||||
svr.ctl = ctl
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) Run() error {
|
||||
err := svr.ctl.Run()
|
||||
func (svr *Service) GetController() *Control {
|
||||
svr.ctlMu.RLock()
|
||||
defer svr.ctlMu.RUnlock()
|
||||
return svr.ctl
|
||||
}
|
||||
|
||||
func (svr *Service) Run(ctx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
svr.ctx = xlog.NewContext(ctx, xlog.New())
|
||||
svr.cancel = cancel
|
||||
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// set custom DNSServer
|
||||
if svr.cfg.DNSServer != "" {
|
||||
dnsAddr := svr.cfg.DNSServer
|
||||
if _, _, err := net.SplitHostPort(dnsAddr); err != nil {
|
||||
dnsAddr = net.JoinHostPort(dnsAddr, "53")
|
||||
}
|
||||
// Change default dns server for frpc
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", dnsAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// login to frps
|
||||
for {
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("login to server failed: %v", err)
|
||||
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and try again to connect to server
|
||||
if lo.FromPtr(svr.cfg.LoginFailExit) {
|
||||
return err
|
||||
}
|
||||
util.RandomSleep(5*time.Second, 0.9, 1.1)
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if svr.cfg.WebServer.Port != 0 {
|
||||
// Init admin server assets
|
||||
assets.Load(svr.cfg.WebServer.AssetsDir)
|
||||
|
||||
address := net.JoinHostPort(svr.cfg.WebServer.Addr, strconv.Itoa(svr.cfg.WebServer.Port))
|
||||
err := svr.RunAdminServer(address)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
log.Info("admin server listen on %s:%d", svr.cfg.WebServer.Addr, svr.cfg.WebServer.Port)
|
||||
}
|
||||
<-svr.ctx.Done()
|
||||
// service context may not be canceled by svr.Close(), we should call it here to release resources
|
||||
if atomic.LoadUint32(&svr.exit) == 0 {
|
||||
svr.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) keepControllerWorking() {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
maxDelayTime := 20 * time.Second
|
||||
delayTime := time.Second
|
||||
|
||||
// if frpc reconnect frps, we need to limit retry times in 1min
|
||||
// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
|
||||
// when exceed 1min, we will reset delay and counts
|
||||
cutoffTime := time.Now().Add(time.Minute)
|
||||
reconnectDelay := time.Second
|
||||
reconnectCounts := 1
|
||||
|
||||
for {
|
||||
<-svr.ctl.ClosedDoneCh()
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// the first three attempts with a low delay
|
||||
if reconnectCounts > 3 {
|
||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||
reconnectDelay *= 2
|
||||
} else {
|
||||
util.RandomSleep(time.Second, 0, 0.5)
|
||||
}
|
||||
reconnectCounts++
|
||||
|
||||
now := time.Now()
|
||||
if now.After(cutoffTime) {
|
||||
// reset
|
||||
cutoffTime = now.Add(time.Minute)
|
||||
reconnectDelay = time.Second
|
||||
reconnectCounts = 1
|
||||
}
|
||||
|
||||
for {
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||
|
||||
delayTime *= 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
}
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// login creates a connection to frps and registers it self as a client
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
cm = NewConnectionManager(svr.ctx, svr.cfg)
|
||||
|
||||
if err = cm.OpenConnection(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cm.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err = cm.Connect()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: svr.cfg.Transport.PoolCount,
|
||||
User: svr.cfg.User,
|
||||
Version: version.Full(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
RunID: svr.runID,
|
||||
Metas: svr.cfg.Metadatas,
|
||||
}
|
||||
|
||||
// Add auth
|
||||
if err = svr.authSetter.SetLogin(loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
xl.Error("%s", loginRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
svr.runID = loginRespMsg.RunID
|
||||
xl.ResetPrefixes()
|
||||
xl.AppendPrefix(svr.runID)
|
||||
|
||||
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
svr.cfgMu.Lock()
|
||||
svr.pxyCfgs = pxyCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl != nil {
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
svr.GracefulClose(time.Duration(0))
|
||||
}
|
||||
|
||||
func (svr *Service) GracefulClose(d time.Duration) {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.GracefulClose(d)
|
||||
svr.ctl = nil
|
||||
}
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if svr.cancel != nil {
|
||||
svr.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectionManager struct {
|
||||
ctx context.Context
|
||||
cfg *v1.ClientCommonConfig
|
||||
|
||||
muxSession *fmux.Session
|
||||
quicConn quic.Connection
|
||||
}
|
||||
|
||||
func NewConnectionManager(ctx context.Context, cfg *v1.ClientCommonConfig) *ConnectionManager {
|
||||
return &ConnectionManager{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) OpenConnection() error {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
|
||||
// special for quic
|
||||
if strings.EqualFold(cm.cfg.Transport.Protocol, "quic") {
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
sn := cm.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
if lo.FromPtr(cm.cfg.Transport.TLS.Enable) {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.Transport.TLS.CertFile,
|
||||
cm.cfg.Transport.TLS.KeyFile,
|
||||
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
} else {
|
||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return err
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
cm.ctx,
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
tlsConfig, &quic.Config{
|
||||
MaxIdleTimeout: time.Duration(cm.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(cm.cfg.Transport.QUIC.MaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(cm.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.quicConn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
if !lo.FromPtr(cm.cfg.Transport.TCPMux) {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := cm.realConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<-svr.closedCh
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||
session, err := fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.muxSession = session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Connect() (net.Conn, error) {
|
||||
if cm.quicConn != nil {
|
||||
stream, err := cm.quicConn.OpenStreamSync(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utilnet.QuicStreamToNetConn(stream, cm.quicConn), nil
|
||||
} else if cm.muxSession != nil {
|
||||
stream, err := cm.muxSession.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
return cm.realConnect()
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
tlsEnable := lo.FromPtr(cm.cfg.Transport.TLS.Enable)
|
||||
if cm.cfg.Transport.Protocol == "wss" {
|
||||
tlsEnable = true
|
||||
}
|
||||
if tlsEnable {
|
||||
sn := cm.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.Transport.TLS.CertFile,
|
||||
cm.cfg.Transport.TLS.KeyFile,
|
||||
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.Transport.ProxyURL)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return nil, err
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := cm.cfg.Transport.Protocol
|
||||
switch protocol {
|
||||
case "websocket":
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket(protocol, "")}))
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(cm.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||
}))
|
||||
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
||||
case "wss":
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithTLSConfigAndPriority(100, tlsConfig))
|
||||
// Make sure that if it is wss, the websocket hook is executed after the tls hook.
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
||||
default:
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(cm.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||
}))
|
||||
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
||||
}
|
||||
|
||||
if cm.cfg.Transport.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.Transport.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(cm.cfg.Transport.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(cm.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
)
|
||||
conn, err := libdial.DialContext(
|
||||
cm.ctx,
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Close() error {
|
||||
if cm.quicConn != nil {
|
||||
_ = cm.quicConn.CloseWithError(0, "")
|
||||
}
|
||||
if cm.muxSession != nil {
|
||||
_ = cm.muxSession.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
135
client/visitor/stcp.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type STCPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
cfg *v1.STCPVisitorConfig
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
if sv.cfg.BindPort > 0 {
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go sv.worker()
|
||||
}
|
||||
|
||||
go sv.internalConnWorker()
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Close() {
|
||||
sv.BaseVisitor.Close()
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("stcp local listener closed")
|
||||
return
|
||||
}
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) internalConnWorker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.internalLn.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("stcp internal listener closed")
|
||||
return
|
||||
}
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
defer userConn.Close()
|
||||
|
||||
xl.Debug("get a new stcp user connection")
|
||||
visitorConn, err := sv.helper.ConnectServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer visitorConn.Close()
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
RunID: sv.helper.RunID(),
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||
UseCompression: sv.cfg.Transport.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.Transport.UseEncryption {
|
||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if sv.cfg.Transport.UseCompression {
|
||||
var recycleFn func()
|
||||
remote, recycleFn = libio.WithCompressionFromPool(remote)
|
||||
defer recycleFn()
|
||||
}
|
||||
|
||||
libio.Join(userConn, remote)
|
||||
}
|
||||
264
client/visitor/sudp.go
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
libio "github.com/fatedier/golib/io"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type SUDPVisitor struct {
|
||||
*BaseVisitor
|
||||
|
||||
checkCloseCh chan struct{}
|
||||
// udpConn is the listener of udp packet
|
||||
udpConn *net.UDPConn
|
||||
readCh chan *msg.UDPPacket
|
||||
sendCh chan *msg.UDPPacket
|
||||
|
||||
cfg *v1.SUDPVisitorConfig
|
||||
}
|
||||
|
||||
// SUDP Run start listen a udp port
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
|
||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||
}
|
||||
|
||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||
|
||||
xl.Info("sudp start to work, listen on %s", addr)
|
||||
|
||||
go sv.dispatcher()
|
||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
var (
|
||||
visitorConn net.Conn
|
||||
err error
|
||||
|
||||
firstPacket *msg.UDPPacket
|
||||
)
|
||||
|
||||
for {
|
||||
select {
|
||||
case firstPacket = <-sv.sendCh:
|
||||
if firstPacket == nil {
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
|
||||
visitorConn, err = sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// visitorConn always be closed when worker done.
|
||||
sv.worker(visitorConn, firstPacket)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
closeCh := make(chan struct{})
|
||||
|
||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||
workConnReaderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
close(closeCh)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
var (
|
||||
rawMsg msg.Message
|
||||
errRet error
|
||||
)
|
||||
|
||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||
return
|
||||
}
|
||||
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.Ping:
|
||||
xl.Debug("frpc visitor get ping message from frpc")
|
||||
continue
|
||||
case *msg.UDPPacket:
|
||||
if errRet := errors.PanicToError(func() {
|
||||
sv.readCh <- m
|
||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||
}); errRet != nil {
|
||||
xl.Info("reader goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||
workConnSenderFn := func(conn net.Conn) {
|
||||
defer func() {
|
||||
conn.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
if firstPacket != nil {
|
||||
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
if !ok {
|
||||
xl.Info("sender goroutine for udp work connection closed")
|
||||
return
|
||||
}
|
||||
|
||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go workConnReaderFn(workConn)
|
||||
go workConnSenderFn(workConn)
|
||||
|
||||
wg.Wait()
|
||||
xl.Info("sudp worker is closed")
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
visitorConn, err := sv.helper.ConnectServer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||
RunID: sv.helper.RunID(),
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||
Timestamp: now,
|
||||
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||
UseCompression: sv.cfg.Transport.UseCompression,
|
||||
}
|
||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||
}
|
||||
|
||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||
}
|
||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||
|
||||
if newVisitorConnRespMsg.Error != "" {
|
||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||
}
|
||||
|
||||
var remote io.ReadWriteCloser
|
||||
remote = visitorConn
|
||||
if sv.cfg.Transport.UseEncryption {
|
||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sv.cfg.Transport.UseCompression {
|
||||
remote = libio.WithCompression(remote)
|
||||
}
|
||||
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) Close() {
|
||||
sv.mu.Lock()
|
||||
defer sv.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
return
|
||||
default:
|
||||
close(sv.checkCloseCh)
|
||||
}
|
||||
sv.BaseVisitor.Close()
|
||||
if sv.udpConn != nil {
|
||||
sv.udpConn.Close()
|
||||
}
|
||||
close(sv.readCh)
|
||||
close(sv.sendCh)
|
||||
}
|
||||
104
client/visitor/visitor.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
// Helper wrapps some functions for visitor to use.
|
||||
type Helper interface {
|
||||
// ConnectServer directly connects to the frp server.
|
||||
ConnectServer() (net.Conn, error)
|
||||
// TransferConn transfers the connection to another visitor.
|
||||
TransferConn(string, net.Conn) error
|
||||
// MsgTransporter returns the message transporter that is used to send and receive messages
|
||||
// to the frp server through the controller.
|
||||
MsgTransporter() transport.MessageTransporter
|
||||
// RunID returns the run id of current controller.
|
||||
RunID() string
|
||||
}
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
type Visitor interface {
|
||||
Run() error
|
||||
AcceptConn(conn net.Conn) error
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewVisitor(
|
||||
ctx context.Context,
|
||||
cfg v1.VisitorConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
helper Helper,
|
||||
) (visitor Visitor) {
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
||||
baseVisitor := BaseVisitor{
|
||||
clientCfg: clientCfg,
|
||||
helper: helper,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
internalLn: utilnet.NewInternalListener(),
|
||||
}
|
||||
switch cfg := cfg.(type) {
|
||||
case *v1.STCPVisitorConfig:
|
||||
visitor = &STCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *v1.XTCPVisitorConfig:
|
||||
visitor = &XTCPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
startTunnelCh: make(chan struct{}),
|
||||
}
|
||||
case *v1.SUDPVisitorConfig:
|
||||
visitor = &SUDPVisitor{
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
checkCloseCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseVisitor struct {
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
helper Helper
|
||||
l net.Listener
|
||||
internalLn *utilnet.InternalListener
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (v *BaseVisitor) AcceptConn(conn net.Conn) error {
|
||||
return v.internalLn.PutConn(conn)
|
||||
}
|
||||
|
||||
func (v *BaseVisitor) Close() {
|
||||
if v.l != nil {
|
||||
v.l.Close()
|
||||
}
|
||||
if v.internalLn != nil {
|
||||
v.internalLn.Close()
|
||||
}
|
||||
}
|
||||
198
client/visitor/visitor_manager.go
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
cfgs map[string]v1.VisitorConfigurer
|
||||
visitors map[string]Visitor
|
||||
helper Helper
|
||||
|
||||
checkInterval time.Duration
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
runID string,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
connectServer func() (net.Conn, error),
|
||||
msgTransporter transport.MessageTransporter,
|
||||
) *Manager {
|
||||
m := &Manager{
|
||||
clientCfg: clientCfg,
|
||||
cfgs: make(map[string]v1.VisitorConfigurer),
|
||||
visitors: make(map[string]Visitor),
|
||||
checkInterval: 10 * time.Second,
|
||||
ctx: ctx,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
m.helper = &visitorHelperImpl{
|
||||
connectServerFn: connectServer,
|
||||
msgTransporter: msgTransporter,
|
||||
transferConnFn: m.TransferConn,
|
||||
runID: runID,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (vm *Manager) Run() {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
|
||||
ticker := time.NewTicker(vm.checkInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-vm.stopCh:
|
||||
xl.Info("gracefully shutdown visitor manager")
|
||||
return
|
||||
case <-ticker.C:
|
||||
vm.mu.Lock()
|
||||
for _, cfg := range vm.cfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, exist := vm.visitors[name]; !exist {
|
||||
xl.Info("try to start visitor [%s]", name)
|
||||
_ = vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
vm.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *Manager) Close() {
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
for _, v := range vm.visitors {
|
||||
v.Close()
|
||||
}
|
||||
select {
|
||||
case <-vm.stopCh:
|
||||
default:
|
||||
close(vm.stopCh)
|
||||
}
|
||||
}
|
||||
|
||||
// Hold lock before calling this function.
|
||||
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
name := cfg.GetBaseConfig().Name
|
||||
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||
err = visitor.Run()
|
||||
if err != nil {
|
||||
xl.Warn("start error: %v", err)
|
||||
} else {
|
||||
vm.visitors[name] = visitor
|
||||
xl.Info("start visitor success")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *Manager) Reload(cfgs []v1.VisitorConfigurer) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
cfgsMap := lo.KeyBy(cfgs, func(c v1.VisitorConfigurer) string {
|
||||
return c.GetBaseConfig().Name
|
||||
})
|
||||
vm.mu.Lock()
|
||||
defer vm.mu.Unlock()
|
||||
|
||||
delNames := make([]string, 0)
|
||||
for name, oldCfg := range vm.cfgs {
|
||||
del := false
|
||||
cfg, ok := cfgsMap[name]
|
||||
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
|
||||
del = true
|
||||
}
|
||||
|
||||
if del {
|
||||
delNames = append(delNames, name)
|
||||
delete(vm.cfgs, name)
|
||||
if visitor, ok := vm.visitors[name]; ok {
|
||||
visitor.Close()
|
||||
}
|
||||
delete(vm.visitors, name)
|
||||
}
|
||||
}
|
||||
if len(delNames) > 0 {
|
||||
xl.Info("visitor removed: %v", delNames)
|
||||
}
|
||||
|
||||
addNames := make([]string, 0)
|
||||
for _, cfg := range cfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, ok := vm.cfgs[name]; !ok {
|
||||
vm.cfgs[name] = cfg
|
||||
addNames = append(addNames, name)
|
||||
_ = vm.startVisitor(cfg)
|
||||
}
|
||||
}
|
||||
if len(addNames) > 0 {
|
||||
xl.Info("visitor added: %v", addNames)
|
||||
}
|
||||
}
|
||||
|
||||
// TransferConn transfers a connection to a visitor.
|
||||
func (vm *Manager) TransferConn(name string, conn net.Conn) error {
|
||||
vm.mu.RLock()
|
||||
defer vm.mu.RUnlock()
|
||||
v, ok := vm.visitors[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("visitor [%s] not found", name)
|
||||
}
|
||||
return v.AcceptConn(conn)
|
||||
}
|
||||
|
||||
type visitorHelperImpl struct {
|
||||
connectServerFn func() (net.Conn, error)
|
||||
msgTransporter transport.MessageTransporter
|
||||
transferConnFn func(name string, conn net.Conn) error
|
||||
runID string
|
||||
}
|
||||
|
||||
func (v *visitorHelperImpl) ConnectServer() (net.Conn, error) {
|
||||
return v.connectServerFn()
|
||||
}
|
||||
|
||||
func (v *visitorHelperImpl) TransferConn(name string, conn net.Conn) error {
|
||||
return v.transferConnFn(name, conn)
|
||||
}
|
||||
|
||||
func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
|
||||
return v.msgTransporter
|
||||
}
|
||||
|
||||
func (v *visitorHelperImpl) RunID() string {
|
||||
return v.runID
|
||||
}
|
||||
457
client/visitor/xtcp.go
Normal file
@@ -0,0 +1,457 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package visitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
var ErrNoTunnelSession = errors.New("no tunnel session")
|
||||
|
||||
type XTCPVisitor struct {
|
||||
*BaseVisitor
|
||||
session TunnelSession
|
||||
startTunnelCh chan struct{}
|
||||
retryLimiter *rate.Limiter
|
||||
cancel context.CancelFunc
|
||||
|
||||
cfg *v1.XTCPVisitorConfig
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.ctx, sv.cancel = context.WithCancel(sv.ctx)
|
||||
|
||||
if sv.cfg.Protocol == "kcp" {
|
||||
sv.session = NewKCPTunnelSession()
|
||||
} else {
|
||||
sv.session = NewQUICTunnelSession(sv.clientCfg)
|
||||
}
|
||||
|
||||
if sv.cfg.BindPort > 0 {
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go sv.worker()
|
||||
}
|
||||
|
||||
go sv.internalConnWorker()
|
||||
go sv.processTunnelStartEvents()
|
||||
if sv.cfg.KeepTunnelOpen {
|
||||
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||
go sv.keepTunnelOpenWorker()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Close() {
|
||||
sv.mu.Lock()
|
||||
defer sv.mu.Unlock()
|
||||
sv.BaseVisitor.Close()
|
||||
if sv.cancel != nil {
|
||||
sv.cancel()
|
||||
}
|
||||
if sv.session != nil {
|
||||
sv.session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) worker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.l.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("xtcp local listener closed")
|
||||
return
|
||||
}
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) internalConnWorker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
for {
|
||||
conn, err := sv.internalLn.Accept()
|
||||
if err != nil {
|
||||
xl.Warn("xtcp internal listener closed")
|
||||
return
|
||||
}
|
||||
go sv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) processTunnelStartEvents() {
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return
|
||||
case <-sv.startTunnelCh:
|
||||
start := time.Now()
|
||||
sv.makeNatHole()
|
||||
duration := time.Since(start)
|
||||
// avoid too frequently
|
||||
if duration < 10*time.Second {
|
||||
time.Sleep(10*time.Second - duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
ticker := time.NewTicker(time.Duration(sv.cfg.MinRetryInterval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
sv.startTunnelCh <- struct{}{}
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
xl.Debug("keepTunnelOpenWorker try to check tunnel...")
|
||||
conn, err := sv.getTunnelConn()
|
||||
if err != nil {
|
||||
xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||
continue
|
||||
}
|
||||
xl.Debug("keepTunnelOpenWorker check success")
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
isConnTrasfered := false
|
||||
defer func() {
|
||||
if !isConnTrasfered {
|
||||
userConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
xl.Debug("get a new xtcp user connection")
|
||||
|
||||
// Open a tunnel connection to the server. If there is already a successful hole-punching connection,
|
||||
// it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
|
||||
ctx := context.Background()
|
||||
if sv.cfg.FallbackTo != "" {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(sv.cfg.FallbackTimeoutMs)*time.Millisecond)
|
||||
defer cancel()
|
||||
ctx = timeoutCtx
|
||||
}
|
||||
tunnelConn, err := sv.openTunnel(ctx)
|
||||
if err != nil {
|
||||
xl.Error("open tunnel error: %v", err)
|
||||
// no fallback, just return
|
||||
if sv.cfg.FallbackTo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
xl.Debug("try to transfer connection to visitor: %s", sv.cfg.FallbackTo)
|
||||
if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil {
|
||||
xl.Error("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
||||
return
|
||||
}
|
||||
isConnTrasfered = true
|
||||
return
|
||||
}
|
||||
|
||||
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||
if sv.cfg.Transport.UseEncryption {
|
||||
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
|
||||
if err != nil {
|
||||
xl.Error("create encryption stream error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if sv.cfg.Transport.UseCompression {
|
||||
var recycleFn func()
|
||||
muxConnRWCloser, recycleFn = libio.WithCompressionFromPool(muxConnRWCloser)
|
||||
defer recycleFn()
|
||||
}
|
||||
|
||||
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
||||
xl.Debug("join connections closed")
|
||||
if len(errs) > 0 {
|
||||
xl.Trace("join connections errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// openTunnel will open a tunnel connection to the target server.
|
||||
func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn, err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
timeoutC := time.After(20 * time.Second)
|
||||
immediateTrigger := make(chan struct{}, 1)
|
||||
defer close(immediateTrigger)
|
||||
immediateTrigger <- struct{}{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sv.ctx.Done():
|
||||
return nil, sv.ctx.Err()
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-immediateTrigger:
|
||||
conn, err = sv.getTunnelConn()
|
||||
case <-ticker.C:
|
||||
conn, err = sv.getTunnelConn()
|
||||
case <-timeoutC:
|
||||
return nil, fmt.Errorf("open tunnel timeout")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err != ErrNoTunnelSession {
|
||||
xl.Warn("get tunnel connection error: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
||||
conn, err := sv.session.OpenConn(sv.ctx)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
sv.session.Close()
|
||||
|
||||
select {
|
||||
case sv.startTunnelCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 0. PreCheck
|
||||
// 1. Prepare
|
||||
// 2. ExchangeInfo
|
||||
// 3. MakeNATHole
|
||||
// 4. Create a tunnel session using an underlying UDP connection.
|
||||
func (sv *XTCPVisitor) makeNatHole() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Trace("makeNatHole start")
|
||||
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||
xl.Warn("nathole precheck error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Trace("nathole prepare start")
|
||||
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||
if err != nil {
|
||||
xl.Warn("nathole prepare error: %v", err)
|
||||
return
|
||||
}
|
||||
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||
|
||||
listenConn := prepareResult.ListenConn
|
||||
|
||||
// send NatHoleVisitor to server
|
||||
now := time.Now().Unix()
|
||||
transactionID := nathole.NewTransactionID()
|
||||
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||
TransactionID: transactionID,
|
||||
ProxyName: sv.cfg.ServerName,
|
||||
Protocol: sv.cfg.Protocol,
|
||||
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||
Timestamp: now,
|
||||
MappedAddrs: prepareResult.Addrs,
|
||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||
}
|
||||
|
||||
xl.Trace("nathole exchange info start")
|
||||
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("nathole exchange info error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||
|
||||
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey))
|
||||
if err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("make hole error: %v", err)
|
||||
return
|
||||
}
|
||||
listenConn = newListenConn
|
||||
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||
|
||||
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||
listenConn.Close()
|
||||
xl.Warn("init tunnel session error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type TunnelSession interface {
|
||||
Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error
|
||||
OpenConn(context.Context) (net.Conn, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
type KCPTunnelSession struct {
|
||||
session *fmux.Session
|
||||
lConn *net.UDPConn
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewKCPTunnelSession() TunnelSession {
|
||||
return &KCPTunnelSession{}
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||
listenConn.Close()
|
||||
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial udp error: %v", err)
|
||||
}
|
||||
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create kcp connection from udp connection error: %v", err)
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
remote.Close()
|
||||
return fmt.Errorf("initial client session error: %v", err)
|
||||
}
|
||||
ks.mu.Lock()
|
||||
ks.session = session
|
||||
ks.lConn = lConn
|
||||
ks.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) OpenConn(_ context.Context) (net.Conn, error) {
|
||||
ks.mu.RLock()
|
||||
defer ks.mu.RUnlock()
|
||||
session := ks.session
|
||||
if session == nil {
|
||||
return nil, ErrNoTunnelSession
|
||||
}
|
||||
return session.Open()
|
||||
}
|
||||
|
||||
func (ks *KCPTunnelSession) Close() {
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
if ks.session != nil {
|
||||
_ = ks.session.Close()
|
||||
ks.session = nil
|
||||
}
|
||||
if ks.lConn != nil {
|
||||
_ = ks.lConn.Close()
|
||||
ks.lConn = nil
|
||||
}
|
||||
}
|
||||
|
||||
type QUICTunnelSession struct {
|
||||
session quic.Connection
|
||||
listenConn *net.UDPConn
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
}
|
||||
|
||||
func NewQUICTunnelSession(clientCfg *v1.ClientCommonConfig) TunnelSession {
|
||||
return &QUICTunnelSession{
|
||||
clientCfg: clientCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||
tlsConfig, err := transport.NewClientTLSConfig("", "", "", raddr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tls config error: %v", err)
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
quicConn, err := quic.Dial(context.Background(), listenConn, raddr, tlsConfig,
|
||||
&quic.Config{
|
||||
MaxIdleTimeout: time.Duration(qs.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(qs.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(qs.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial quic error: %v", err)
|
||||
}
|
||||
qs.mu.Lock()
|
||||
qs.session = quicConn
|
||||
qs.listenConn = listenConn
|
||||
qs.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
session := qs.session
|
||||
if session == nil {
|
||||
return nil, ErrNoTunnelSession
|
||||
}
|
||||
stream, err := session.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utilnet.QuicStreamToNetConn(stream, session), nil
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) Close() {
|
||||
qs.mu.Lock()
|
||||
defer qs.mu.Unlock()
|
||||
if qs.session != nil {
|
||||
_ = qs.session.CloseWithError(0, "")
|
||||
qs.session = nil
|
||||
}
|
||||
if qs.listenConn != nil {
|
||||
_ = qs.listenConn.Close()
|
||||
qs.listenConn = nil
|
||||
}
|
||||
}
|
||||
106
cmd/frpc/main.go
@@ -15,110 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
_ "github.com/fatedier/frp/assets/frpc"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string = "./frpc.ini"
|
||||
)
|
||||
|
||||
var usage string = `frpc is the client of frp
|
||||
|
||||
Usage:
|
||||
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
||||
frpc -h | --help
|
||||
frpc -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
-L log_file set output log file, including console
|
||||
--log-level=<log_level> set log level: debug, info, warn, error
|
||||
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
|
||||
-h --help show this screen
|
||||
-v --version show version
|
||||
`
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
confFile := "./frpc.ini"
|
||||
// the configures parsed from file will be replaced by those from command line if exist
|
||||
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
||||
|
||||
if args["-c"] != nil {
|
||||
confFile = args["-c"].(string)
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(confFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if args["-L"] != nil {
|
||||
if args["-L"].(string) == "console" {
|
||||
config.ClientCommonCfg.LogWay = "console"
|
||||
} else {
|
||||
config.ClientCommonCfg.LogWay = "file"
|
||||
config.ClientCommonCfg.LogFile = args["-L"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["--log-level"] != nil {
|
||||
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
|
||||
}
|
||||
|
||||
if args["--server-addr"] != nil {
|
||||
addr := strings.Split(args["--server-addr"].(string), ":")
|
||||
if len(addr) != 2 {
|
||||
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ClientCommonCfg.ServerAddr = addr[0]
|
||||
config.ClientCommonCfg.ServerPort = serverPort
|
||||
}
|
||||
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
pxyCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
||||
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
||||
|
||||
svr := client.NewService(pxyCfgs)
|
||||
err = svr.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sub.Execute()
|
||||
}
|
||||
|
||||
117
cmd/frpc/sub/admin.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rodaine/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewAdminCommand(
|
||||
"reload",
|
||||
"Hot-Reload frpc configuration",
|
||||
ReloadHandler,
|
||||
))
|
||||
|
||||
rootCmd.AddCommand(NewAdminCommand(
|
||||
"status",
|
||||
"Overview of all proxies status",
|
||||
StatusHandler,
|
||||
))
|
||||
|
||||
rootCmd.AddCommand(NewAdminCommand(
|
||||
"stop",
|
||||
"Stop the running frpc",
|
||||
StopHandler,
|
||||
))
|
||||
}
|
||||
|
||||
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: name,
|
||||
Short: short,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if cfg.WebServer.Port <= 0 {
|
||||
fmt.Println("web server port should be set if you want to use this feature")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := handler(cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||
if err := client.Reload(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("reload success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||
res, err := client.GetAllProxyStatus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Proxy Status...\n\n")
|
||||
for _, typ := range proxyTypes {
|
||||
arrs := res[string(typ)]
|
||||
if len(arrs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(strings.ToUpper(string(typ)))
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range arrs {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||
if err := client.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("stop success")
|
||||
return nil
|
||||
}
|
||||
125
cmd/frpc/sub/flags.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
|
||||
type BandwidthQuantityFlag struct {
|
||||
V *types.BandwidthQuantity
|
||||
}
|
||||
|
||||
func (f *BandwidthQuantityFlag) Set(s string) error {
|
||||
return f.V.UnmarshalString(s)
|
||||
}
|
||||
|
||||
func (f *BandwidthQuantityFlag) String() string {
|
||||
return f.V.String()
|
||||
}
|
||||
|
||||
func (f *BandwidthQuantityFlag) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func RegisterProxyFlags(cmd *cobra.Command, c v1.ProxyConfigurer) {
|
||||
registerProxyBaseConfigFlags(cmd, c.GetBaseConfig())
|
||||
switch cc := c.(type) {
|
||||
case *v1.TCPProxyConfig:
|
||||
cmd.Flags().IntVarP(&cc.RemotePort, "remote_port", "r", 0, "remote port")
|
||||
case *v1.UDPProxyConfig:
|
||||
cmd.Flags().IntVarP(&cc.RemotePort, "remote_port", "r", 0, "remote port")
|
||||
case *v1.HTTPProxyConfig:
|
||||
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||
cmd.Flags().StringSliceVarP(&cc.Locations, "locations", "", []string{}, "locations")
|
||||
cmd.Flags().StringVarP(&cc.HTTPUser, "http_user", "", "", "http auth user")
|
||||
cmd.Flags().StringVarP(&cc.HTTPPassword, "http_pwd", "", "", "http auth password")
|
||||
cmd.Flags().StringVarP(&cc.HostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
||||
case *v1.HTTPSProxyConfig:
|
||||
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||
case *v1.TCPMuxProxyConfig:
|
||||
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||
cmd.Flags().StringVarP(&cc.Multiplexer, "mux", "", "", "multiplexer")
|
||||
case *v1.STCPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
case *v1.SUDPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
case *v1.XTCPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
}
|
||||
}
|
||||
|
||||
func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
||||
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||
cmd.Flags().IntVarP(&c.LocalPort, "local_port", "l", 0, "local port")
|
||||
cmd.Flags().BoolVarP(&c.Transport.UseEncryption, "ue", "", false, "use encryption")
|
||||
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||
cmd.Flags().StringVarP(&c.Transport.BandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||
cmd.Flags().VarP(&BandwidthQuantityFlag{V: &c.Transport.BandwidthLimit}, "bandwidth_limit", "", "bandwidth limit (e.g. 100KB or 1MB)")
|
||||
}
|
||||
|
||||
func registerProxyDomainConfigFlags(cmd *cobra.Command, c *v1.DomainConfig) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
cmd.Flags().StringSliceVarP(&c.CustomDomains, "custom_domain", "d", []string{}, "custom domains")
|
||||
cmd.Flags().StringVarP(&c.SubDomain, "sd", "", "", "sub domain")
|
||||
}
|
||||
|
||||
func RegisterVisitorFlags(cmd *cobra.Command, c v1.VisitorConfigurer) {
|
||||
registerVisitorBaseConfigFlags(cmd, c.GetBaseConfig())
|
||||
|
||||
// add visitor flags if exist
|
||||
}
|
||||
|
||||
func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
cmd.Flags().StringVarP(&c.Name, "visitor_name", "n", "", "visitor name")
|
||||
cmd.Flags().BoolVarP(&c.Transport.UseEncryption, "ue", "", false, "use encryption")
|
||||
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
||||
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
||||
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
||||
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
||||
}
|
||||
|
||||
func RegisterClientCommonConfigFlags(cmd *cobra.Command, c *v1.ClientCommonConfig) {
|
||||
cmd.PersistentFlags().StringVarP(&c.ServerAddr, "server_addr", "s", "127.0.0.1", "frp server's address")
|
||||
cmd.PersistentFlags().IntVarP(&c.ServerPort, "server_port", "P", 7000, "frp server's port")
|
||||
cmd.PersistentFlags().StringVarP(&c.User, "user", "u", "", "user")
|
||||
cmd.PersistentFlags().StringVarP(&c.Transport.Protocol, "protocol", "p", "tcp",
|
||||
fmt.Sprintf("optional values are %v", validation.SupportedTransportProtocols))
|
||||
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
|
||||
cmd.PersistentFlags().StringVarP(&c.Log.Level, "log_level", "", "info", "log level")
|
||||
cmd.PersistentFlags().StringVarP(&c.Log.To, "log_file", "", "console", "console or file path")
|
||||
cmd.PersistentFlags().Int64VarP(&c.Log.MaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||
cmd.PersistentFlags().BoolVarP(&c.Log.DisablePrintColor, "disable_log_color", "", false, "disable log color in console")
|
||||
cmd.PersistentFlags().StringVarP(&c.Transport.TLS.ServerName, "tls_server_name", "", "", "specify the custom server name of tls certificate")
|
||||
cmd.PersistentFlags().StringVarP(&c.DNSServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
||||
|
||||
c.Transport.TLS.Enable = cmd.PersistentFlags().BoolP("tls_enable", "", true, "enable frpc tls")
|
||||
}
|
||||
96
cmd/frpc/sub/nathole.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
)
|
||||
|
||||
var (
|
||||
natHoleSTUNServer string
|
||||
natHoleLocalAddr string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(natholeCmd)
|
||||
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
||||
|
||||
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "", "STUN server address for nathole")
|
||||
natholeCmd.PersistentFlags().StringVarP(&natHoleLocalAddr, "nat_hole_local_addr", "l", "", "local address to connect STUN server")
|
||||
}
|
||||
|
||||
var natholeCmd = &cobra.Command{
|
||||
Use: "nathole",
|
||||
Short: "Actions about nathole",
|
||||
}
|
||||
|
||||
var natholeDiscoveryCmd = &cobra.Command{
|
||||
Use: "discover",
|
||||
Short: "Discover nathole information from stun server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// ignore error here, because we can use command line pameters
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
cfg = &v1.ClientCommonConfig{}
|
||||
}
|
||||
if natHoleSTUNServer != "" {
|
||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||
}
|
||||
|
||||
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addrs, localAddr, err := nathole.Discover([]string{cfg.NatHoleSTUNServer}, natHoleLocalAddr)
|
||||
if err != nil {
|
||||
fmt.Println("discover error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(addrs) < 2 {
|
||||
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addrs)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
localIPs, _ := nathole.ListLocalIPsForNatHole(10)
|
||||
|
||||
natFeature, err := nathole.ClassifyNATFeature(addrs, localIPs)
|
||||
if err != nil {
|
||||
fmt.Println("classify nat feature error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("STUN server:", cfg.NatHoleSTUNServer)
|
||||
fmt.Println("Your NAT type is:", natFeature.NatType)
|
||||
fmt.Println("Behavior is:", natFeature.Behavior)
|
||||
fmt.Println("External address is:", addrs)
|
||||
fmt.Println("Local address is:", localAddr.String())
|
||||
fmt.Println("Public Network:", natFeature.PublicNetwork)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func validateForNatHoleDiscovery(cfg *v1.ClientCommonConfig) error {
|
||||
if cfg.NatHoleSTUNServer == "" {
|
||||
return fmt.Errorf("nat_hole_stun_server can not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
120
cmd/frpc/sub/proxy.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
|
||||
var proxyTypes = []v1.ProxyType{
|
||||
v1.ProxyTypeTCP,
|
||||
v1.ProxyTypeUDP,
|
||||
v1.ProxyTypeTCPMUX,
|
||||
v1.ProxyTypeHTTP,
|
||||
v1.ProxyTypeHTTPS,
|
||||
v1.ProxyTypeSTCP,
|
||||
v1.ProxyTypeSUDP,
|
||||
v1.ProxyTypeXTCP,
|
||||
}
|
||||
|
||||
var visitorTypes = []v1.VisitorType{
|
||||
v1.VisitorTypeSTCP,
|
||||
v1.VisitorTypeSUDP,
|
||||
v1.VisitorTypeXTCP,
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, typ := range proxyTypes {
|
||||
c := v1.NewProxyConfigurerByType(typ)
|
||||
if c == nil {
|
||||
panic("proxy type: " + typ + " not support")
|
||||
}
|
||||
clientCfg := v1.ClientCommonConfig{}
|
||||
cmd := NewProxyCommand(string(typ), c, &clientCfg)
|
||||
RegisterClientCommonConfigFlags(cmd, &clientCfg)
|
||||
RegisterProxyFlags(cmd, c)
|
||||
|
||||
// add sub command for visitor
|
||||
if lo.Contains(visitorTypes, v1.VisitorType(typ)) {
|
||||
vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ))
|
||||
if vc == nil {
|
||||
panic("visitor type: " + typ + " not support")
|
||||
}
|
||||
visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg)
|
||||
RegisterVisitorFlags(visitorCmd, vc)
|
||||
cmd.AddCommand(visitorCmd)
|
||||
}
|
||||
rootCmd.AddCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: name,
|
||||
Short: fmt.Sprintf("Run frpc with a single %s proxy", name),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientCfg.Complete()
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c.Complete(clientCfg.User)
|
||||
c.GetBaseConfig().Type = name
|
||||
if err := validation.ValidateProxyConfigurerForClient(c); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "visitor",
|
||||
Short: fmt.Sprintf("Run frpc with a single %s visitor", name),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientCfg.Complete()
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c.Complete(clientCfg)
|
||||
c.GetBaseConfig().Type = name
|
||||
if err := validation.ValidateVisitorConfigurer(c); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
155
cmd/frpc/sub/root.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
showVersion bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frpc",
|
||||
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if showVersion {
|
||||
fmt.Println(version.Full())
|
||||
return nil
|
||||
}
|
||||
|
||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||
if cfgDir != "" {
|
||||
_ = runMultipleClients(cfgDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not show command usage here.
|
||||
err := runClient(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func runMultipleClients(cfgDir string) error {
|
||||
var wg sync.WaitGroup
|
||||
err := filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
wg.Add(1)
|
||||
time.Sleep(time.Millisecond)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := runClient(path)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc service error for config file [%s]\n", path)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTermSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.GracefulClose(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) error {
|
||||
cfg, pxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isLegacyFormat {
|
||||
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||
"please use yaml/json/toml format instead!\n")
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateAllClientConfig(cfg, pxyCfgs, visitorCfgs)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||
}
|
||||
|
||||
func startService(
|
||||
cfg *v1.ClientCommonConfig,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
cfgFile string,
|
||||
) error {
|
||||
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
||||
|
||||
if cfgFile != "" {
|
||||
log.Info("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
||||
}
|
||||
svr, err := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shouldGracefulClose := cfg.Transport.Protocol == "kcp" || cfg.Transport.Protocol == "quic"
|
||||
// Capture the exit signal if we use kcp or quic.
|
||||
if shouldGracefulClose {
|
||||
go handleTermSignal(svr)
|
||||
}
|
||||
|
||||
_ = svr.Run(context.Background())
|
||||
return nil
|
||||
}
|
||||
57
cmd/frpc/sub/verify.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2021 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(verifyCmd)
|
||||
}
|
||||
|
||||
var verifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify that the configures is valid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cfgFile == "" {
|
||||
fmt.Println("frpc: the configuration file is not specified")
|
||||
return nil
|
||||
}
|
||||
|
||||
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
warning, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
110
cmd/frps/flags.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
type PortsRangeSliceFlag struct {
|
||||
V *[]types.PortsRange
|
||||
}
|
||||
|
||||
func (f *PortsRangeSliceFlag) String() string {
|
||||
if f.V == nil {
|
||||
return ""
|
||||
}
|
||||
return types.PortsRangeSlice(*f.V).String()
|
||||
}
|
||||
|
||||
func (f *PortsRangeSliceFlag) Set(s string) error {
|
||||
slice, err := types.NewPortsRangeSliceFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f.V = slice
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *PortsRangeSliceFlag) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
type BoolFuncFlag struct {
|
||||
TrueFunc func()
|
||||
FalseFunc func()
|
||||
|
||||
v bool
|
||||
}
|
||||
|
||||
func (f *BoolFuncFlag) String() string {
|
||||
return strconv.FormatBool(f.v)
|
||||
}
|
||||
|
||||
func (f *BoolFuncFlag) Set(s string) error {
|
||||
f.v = strconv.FormatBool(f.v) == "true"
|
||||
|
||||
if !f.v {
|
||||
if f.FalseFunc != nil {
|
||||
f.FalseFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.TrueFunc != nil {
|
||||
f.TrueFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *BoolFuncFlag) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig) {
|
||||
cmd.PersistentFlags().StringVarP(&c.BindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||
cmd.PersistentFlags().IntVarP(&c.BindPort, "bind_port", "p", 7000, "bind port")
|
||||
cmd.PersistentFlags().IntVarP(&c.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||
cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||
cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
||||
cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
||||
cmd.PersistentFlags().Int64VarP(&c.VhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||
cmd.PersistentFlags().StringVarP(&c.WebServer.Addr, "dashboard_addr", "", "0.0.0.0", "dashboard address")
|
||||
cmd.PersistentFlags().IntVarP(&c.WebServer.Port, "dashboard_port", "", 0, "dashboard port")
|
||||
cmd.PersistentFlags().StringVarP(&c.WebServer.User, "dashboard_user", "", "admin", "dashboard user")
|
||||
cmd.PersistentFlags().StringVarP(&c.WebServer.Password, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
cmd.PersistentFlags().BoolVarP(&c.EnablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
|
||||
cmd.PersistentFlags().StringVarP(&c.Log.To, "log_file", "", "console", "log file")
|
||||
cmd.PersistentFlags().StringVarP(&c.Log.Level, "log_level", "", "info", "log level")
|
||||
cmd.PersistentFlags().Int64VarP(&c.Log.MaxDays, "log_max_days", "", 3, "log max days")
|
||||
cmd.PersistentFlags().BoolVarP(&c.Log.DisablePrintColor, "disable_log_color", "", false, "disable log color in console")
|
||||
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
|
||||
cmd.PersistentFlags().StringVarP(&c.SubDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||
cmd.PersistentFlags().VarP(&PortsRangeSliceFlag{V: &c.AllowPorts}, "allow_ports", "", "allow ports")
|
||||
cmd.PersistentFlags().Int64VarP(&c.MaxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
||||
cmd.PersistentFlags().BoolVarP(&c.Transport.TLS.Force, "tls_only", "", false, "frps tls only")
|
||||
|
||||
webServerTLS := v1.TLSConfig{}
|
||||
cmd.PersistentFlags().StringVarP(&webServerTLS.CertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
|
||||
cmd.PersistentFlags().StringVarP(&webServerTLS.KeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
|
||||
cmd.PersistentFlags().VarP(&BoolFuncFlag{
|
||||
TrueFunc: func() { c.WebServer.TLS = &webServerTLS },
|
||||
}, "dashboard_tls_mode", "", "if enable dashboard tls mode")
|
||||
}
|
||||
103
cmd/frps/main.go
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,104 +15,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/server"
|
||||
"github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
_ "github.com/fatedier/frp/assets/frps"
|
||||
_ "github.com/fatedier/frp/pkg/metrics"
|
||||
)
|
||||
|
||||
var usage string = `frps is the server of frp
|
||||
|
||||
Usage:
|
||||
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
|
||||
frps -h | --help
|
||||
frps -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
-L log_file set output log file, including console
|
||||
--log-level=<log_level> set log level: debug, info, warn, error
|
||||
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
|
||||
-h --help show this screen
|
||||
-v --version show version
|
||||
`
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
confFile := "./frps.ini"
|
||||
// the configures parsed from file will be replaced by those from command line if exist
|
||||
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
||||
|
||||
if args["-c"] != nil {
|
||||
confFile = args["-c"].(string)
|
||||
}
|
||||
|
||||
conf, err := ini.LoadFile(confFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if args["-L"] != nil {
|
||||
if args["-L"].(string) == "console" {
|
||||
config.ServerCommonCfg.LogWay = "console"
|
||||
} else {
|
||||
config.ServerCommonCfg.LogWay = "file"
|
||||
config.ServerCommonCfg.LogFile = args["-L"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["--log-level"] != nil {
|
||||
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
|
||||
}
|
||||
|
||||
if args["--addr"] != nil {
|
||||
addr := strings.Split(args["--addr"].(string), ":")
|
||||
if len(addr) != 2 {
|
||||
fmt.Println("--addr format error: example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("--addr format error, example 0.0.0.0:7000")
|
||||
os.Exit(1)
|
||||
}
|
||||
config.ServerCommonCfg.BindAddr = addr[0]
|
||||
config.ServerCommonCfg.BindPort = bindPort
|
||||
}
|
||||
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
|
||||
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
|
||||
|
||||
svr, err := server.NewService()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("Start frps success")
|
||||
if config.ServerCommonCfg.PrivilegeMode == true {
|
||||
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
|
||||
}
|
||||
server.ServerService = svr
|
||||
svr.Run()
|
||||
crypto.DefaultSalt = "frp"
|
||||
Execute()
|
||||
}
|
||||
|
||||
114
cmd/frps/root.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
|
||||
serverCfg v1.ServerConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||
|
||||
RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frps",
|
||||
Short: "frps is the server of frp (https://github.com/fatedier/frp)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if showVersion {
|
||||
fmt.Println(version.Full())
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
svrCfg *v1.ServerConfig
|
||||
isLegacyFormat bool
|
||||
err error
|
||||
)
|
||||
if cfgFile != "" {
|
||||
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if isLegacyFormat {
|
||||
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||
"please use yaml/json/toml format instead!\n")
|
||||
}
|
||||
} else {
|
||||
serverCfg.Complete()
|
||||
svrCfg = &serverCfg
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := runServer(svrCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runServer(cfg *v1.ServerConfig) (err error) {
|
||||
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
||||
|
||||
if cfgFile != "" {
|
||||
log.Info("frps uses config file: %s", cfgFile)
|
||||
} else {
|
||||
log.Info("frps uses command line arguments for config")
|
||||
}
|
||||
|
||||
svr, err := server.NewService(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("frps started successfully")
|
||||
svr.Run(context.Background())
|
||||
return
|
||||
}
|
||||
56
cmd/frps/verify.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2021 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(verifyCmd)
|
||||
}
|
||||
|
||||
var verifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify that the configures is valid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cfgFile == "" {
|
||||
fmt.Println("frps: the configuration file is not specified")
|
||||
return nil
|
||||
}
|
||||
svrCfg, _, err := config.LoadServerConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 7000
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
360
conf/frpc.toml
Normal file
@@ -0,0 +1,360 @@
|
||||
# your proxy name will be changed to {user}.{proxy}
|
||||
user = "your_name"
|
||||
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single serverAddr field, no need square brackets, like serverAddr = "::".
|
||||
serverAddr = "0.0.0.0"
|
||||
serverPort = 7000
|
||||
|
||||
# STUN server to help penetrate NAT hole.
|
||||
# natHoleStunServer = "stun.easyvoip.com:3478"
|
||||
|
||||
# Decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||
# default is true
|
||||
loginFailExit = true
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log.to = "./frpc.log"
|
||||
# trace, debug, info, warn, error
|
||||
log.level = "info"
|
||||
log.maxDays = 3
|
||||
# disable log colors when log.to is console, default is false
|
||||
log.disablePrintColor = false
|
||||
|
||||
auth.method = "token"
|
||||
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||
# Optional values are HeartBeats, NewWorkConns.
|
||||
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||
|
||||
# auth token
|
||||
auth.token = "12345678"
|
||||
|
||||
# oidc.clientID specifies the client ID to use to get a token in OIDC authentication.
|
||||
# auth.oidc.clientID = ""
|
||||
# oidc.clientSecret specifies the client secret to use to get a token in OIDC authentication.
|
||||
# auth.oidc.clientSecret = ""
|
||||
# oidc.audience specifies the audience of the token in OIDC authentication.
|
||||
# auth.oidc.audience = ""
|
||||
# oidc_scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
# auth.oidc.scope = ""
|
||||
# oidc.tokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
# It will be used to get an OIDC token.
|
||||
# auth.oidc.tokenEndpointURL = ""
|
||||
|
||||
# oidc.additionalEndpointParams specifies additional parameters to be sent to the OIDC Token Endpoint.
|
||||
# For example, if you want to specify the "audience" parameter, you can set as follow.
|
||||
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
|
||||
# auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/"
|
||||
# auth.oidc.additionalEndpointParams.var1 = "foobar"
|
||||
|
||||
# Set admin address for control frpc's action by http api such as reload
|
||||
webServer.addr = "127.0.0.1"
|
||||
webServer.port = 7400
|
||||
webServer.user = "admin"
|
||||
webServer.password = "admin"
|
||||
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||
# webServer.assetsDir = "./static"
|
||||
|
||||
# Enable golang pprof handlers in admin listener.
|
||||
webServer.pprofEnable = false
|
||||
|
||||
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||
# transport.dialServerTimeout = 10
|
||||
|
||||
# dialServerKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# transport.dialServerKeepalive = 7200
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
transport.poolCount = 5
|
||||
|
||||
# If tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
# transport.tcpMux = true
|
||||
|
||||
# Specify keep alive interval for tcp mux.
|
||||
# only valid if tcpMux is enabled.
|
||||
# transport.tcpMuxKeepaliveInterval = 60
|
||||
|
||||
# Communication protocol used to connect to server
|
||||
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||
transport.protocol = "tcp"
|
||||
|
||||
# set client binding ip when connect server, default is empty.
|
||||
# only when protocol = tcp or websocket, the value will be used.
|
||||
transport.connectServerLocalIP = "0.0.0.0"
|
||||
|
||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set proxyURL here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# transport.proxyURL = "http://user:passwd@192.168.1.128:8080"
|
||||
# transport.proxyURL = "socks5://user:passwd@192.168.1.128:1080"
|
||||
# transport.proxyURL = "ntlm://user:passwd@192.168.1.128:2080"
|
||||
|
||||
# quic protocol options
|
||||
# transport.quic.keepalivePeriod = 10
|
||||
# transport.quic.maxIdleTimeout = 30
|
||||
# transport.quic.maxIncomingStreams = 100000
|
||||
|
||||
# If tls.enable is true, frpc will connect frps by tls.
|
||||
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||
transport.tls.enable = true
|
||||
|
||||
# transport.tls.certFile = "client.crt"
|
||||
# transport.tls.keyFile = "client.key"
|
||||
# transport.tls.trustedCaFile = "ca.crt"
|
||||
# transport.tls.serverName = "example.com"
|
||||
|
||||
# If the disableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
|
||||
# first custom byte when tls is enabled.
|
||||
# Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||
# transport.tls.disableCustomTLSFirstByte = true
|
||||
|
||||
# Heartbeat configure, it's not recommended to modify the default value.
|
||||
# The default value of heartbeat_interval is 10 and heartbeat_timeout is 90. Set negative value
|
||||
# to disable it.
|
||||
# transport.heartbeatInterval = 30
|
||||
# transport.heartbeatTimeout = 90
|
||||
|
||||
# Specify a dns server, so frpc will use this instead of default one
|
||||
# dnsServer = "8.8.8.8"
|
||||
|
||||
# Proxy names you want to start.
|
||||
# Default is empty, means all proxies.
|
||||
# start = ["ssh", "dns"]
|
||||
|
||||
# Specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udpPacketSize = 1500
|
||||
|
||||
# Additional metadatas for client.
|
||||
metadatas.var1 = "abc"
|
||||
metadatas.var2 = "123"
|
||||
|
||||
# Include other config files for proxies.
|
||||
# includes = ["./confd/*.ini"]
|
||||
|
||||
[[proxies]]
|
||||
# 'ssh' is the unique proxy name
|
||||
# If global user is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||
name = "ssh"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 22
|
||||
# Limit bandwidth for this proxy, unit is KB and MB
|
||||
transport.bandwidthLimit = "1MB"
|
||||
# Where to limit bandwidth, can be 'client' or 'server', default is 'client'
|
||||
transport.bandwidthLimitMode = "client"
|
||||
# If true, traffic of this proxy will be encrypted, default is false
|
||||
transport.useEncryption = false
|
||||
# If true, traffic will be compressed
|
||||
transport.useCompression = false
|
||||
# Remote port listen by frps
|
||||
remotePort = 6001
|
||||
# frps will load balancing connections for proxies in same group
|
||||
loadBalancer.group = "test_group"
|
||||
# group should have same group key
|
||||
loadBalancer.groupKey = "123456"
|
||||
# Enable health check for the backend service, it supports 'tcp' and 'http' now.
|
||||
# frpc will connect local service's port to detect it's healthy status
|
||||
healthCheck.type = "tcp"
|
||||
# Health check connection timeout
|
||||
healthCheck.timeoutSeconds = 3
|
||||
# If continuous failed in 3 times, the proxy will be removed from frps
|
||||
healthCheck.maxFailed = 3
|
||||
# every 10 seconds will do a health check
|
||||
healthCheck.intervalSeconds = 10
|
||||
# additional meta info for each proxy
|
||||
metadatas.var1 = "abc"
|
||||
metadatas.var2 = "123"
|
||||
|
||||
[[proxies]]
|
||||
name = "ssh_random"
|
||||
type = "tcp"
|
||||
localIP = "192.168.31.100"
|
||||
localPort = 22
|
||||
# If remote_port is 0, frps will assign a random port for you
|
||||
remotePort = 0
|
||||
|
||||
[[proxies]]
|
||||
name = "dns"
|
||||
type = "udp"
|
||||
localIP = "114.114.114.114"
|
||||
localPort = 53
|
||||
remotePort = 6002
|
||||
|
||||
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||
[[proxies]]
|
||||
name = "web01"
|
||||
type = "http"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 80
|
||||
# http username and password are safety certification for http protocol
|
||||
# if not set, you can access this custom_domains without certification
|
||||
httpUser = "admin"
|
||||
httpPassword = "admin"
|
||||
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
|
||||
subdomain = "web01"
|
||||
customDomains = ["web01.yourdomain.com"]
|
||||
# locations is only available for http type
|
||||
locations = ["/", "/pic"]
|
||||
# route requests to this service if http basic auto user is abc
|
||||
# route_by_http_user = abc
|
||||
hostHeaderRewrite = "example.com"
|
||||
# params with prefix "header_" will be used to update http request headers
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
healthCheck.type = "http"
|
||||
# frpc will send a GET http request '/status' to local http service
|
||||
# http service is alive when it return 2xx http response code
|
||||
healthCheck.path = "/status"
|
||||
healthCheck.intervalSeconds = 10
|
||||
healthCheck.maxFailed = 3
|
||||
healthCheck.timeoutSeconds = 3
|
||||
|
||||
[[proxies]]
|
||||
name = "web02"
|
||||
type = "https"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 8000
|
||||
subdomain = "web02"
|
||||
customDomains = ["web02.yourdomain.com"]
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
transport.proxyProtocolVersion = "v2"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcpmuxhttpconnect"
|
||||
type = "tcpmux"
|
||||
multiplexer = "httpconnect"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 10701
|
||||
customDomains = ["tunnel1"]
|
||||
# routeByHTTPUser = "user1"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_unix_domain_socket"
|
||||
type = "tcp"
|
||||
remotePort = 6003
|
||||
# if plugin is defined, local_ip and local_port is useless
|
||||
# plugin will handle connections got from frps
|
||||
[proxies.plugin]
|
||||
type = "unix_domain_socket"
|
||||
unixPath = "/var/run/docker.sock"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_http_proxy"
|
||||
type = "tcp"
|
||||
remotePort = 6004
|
||||
[proxies.plugin]
|
||||
type = "http_proxy"
|
||||
httpUser = "abc"
|
||||
httpPassword = "abc"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_socks5"
|
||||
type = "tcp"
|
||||
remotePort = 6005
|
||||
[proxies.plugin]
|
||||
type = "socks5"
|
||||
username = "abc"
|
||||
password = "abc"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_static_file"
|
||||
type = "tcp"
|
||||
remotePort = 6006
|
||||
[proxies.plugin]
|
||||
type = "static_file"
|
||||
localPath = "/var/www/blog"
|
||||
stripPrefix = "static"
|
||||
httpUser = "abc"
|
||||
httpPassword = "abc"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_https2http"
|
||||
type = "https"
|
||||
customDomains = ["test.yourdomain.com"]
|
||||
[proxies.plugin]
|
||||
type = "https2http"
|
||||
localAddr = "127.0.0.1:80"
|
||||
crtPath = "./server.crt"
|
||||
keyPath = "./server.key"
|
||||
hostHeaderRewrite = "127.0.0.1"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_https2https"
|
||||
type = "https"
|
||||
customDomains = ["test.yourdomain.com"]
|
||||
[proxies.plugin]
|
||||
type = "https2https"
|
||||
localAddr = "127.0.0.1:443"
|
||||
crtPath = "./server.crt"
|
||||
keyPath = "./server.key"
|
||||
hostHeaderRewrite = "127.0.0.1"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
|
||||
[[proxies]]
|
||||
name = "plugin_http2https"
|
||||
type = "http"
|
||||
customDomains = ["test.yourdomain.com"]
|
||||
[proxies.plugin]
|
||||
type = "http2https"
|
||||
localAddr = "127.0.0.1:443"
|
||||
hostHeaderRewrite = "127.0.0.1"
|
||||
requestHeaders.set.x-from-where = "frp"
|
||||
|
||||
[[proxies]]
|
||||
name = "secret_tcp"
|
||||
# If the type is secret tcp, remote_port is useless
|
||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||
type = "stcp"
|
||||
# secretKey is used for authentication for visitors
|
||||
secretKey = "abcdefg"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 22
|
||||
# If not empty, only visitors from specified users can connect.
|
||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||
allowUsers = ["*"]
|
||||
|
||||
[[proxies]]
|
||||
name = "p2p_tcp"
|
||||
type = "xtcp"
|
||||
secretKey = "abcdefg"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 22
|
||||
# If not empty, only visitors from specified users can connect.
|
||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||
allowUsers = ["user1", "user2"]
|
||||
|
||||
# frpc role visitor -> frps -> frpc role server
|
||||
[[visitors]]
|
||||
name = "secret_tcp_visitor"
|
||||
type = "stcp"
|
||||
# the server name you want to visitor
|
||||
serverName = "secret_tcp"
|
||||
secretKey = "abcdefg"
|
||||
# connect this address to visitor stcp server
|
||||
bindAddr = "127.0.0.1"
|
||||
# bindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
# other visitors. (This is not supported for SUDP now)
|
||||
bindPort = 9000
|
||||
|
||||
[[visitors]]
|
||||
name = "p2p_tcp_visitor"
|
||||
type = "xtcp"
|
||||
# if the server user is not set, it defaults to the current user
|
||||
serverUser = "user1"
|
||||
serverName = "p2p_tcp"
|
||||
secretKey = "abcdefg"
|
||||
bindAddr = "127.0.0.1"
|
||||
# bindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
# other visitors. (This is not supported for SUDP now)
|
||||
bindPort = 9001
|
||||
# when automatic tunnel persistence is required, set it to true
|
||||
keepTunnelOpen = false
|
||||
# effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour
|
||||
maxRetriesAnHour = 8
|
||||
minRetryInterval = 90
|
||||
# fallbackTo = "stcp_visitor"
|
||||
# fallbackTimeoutMs = 500
|
||||
@@ -1,112 +0,0 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:pwd@192.168.1.128:8080
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# for authentication
|
||||
privilege_token = 12345678
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
|
||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
tcp_mux = true
|
||||
|
||||
# your proxy name will be changed to {user}.{proxy}
|
||||
user = your_name
|
||||
|
||||
# decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||
# default is true
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# now it supports tcp and kcp, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# proxy names you want to start divided by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# ssh is the proxy name same as server's configuration
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
|
||||
[ssh]
|
||||
# tcp | udp | http | https, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||
use_encryption = false
|
||||
# if true, message will be compressed
|
||||
use_compression = false
|
||||
# remote port listen by frps
|
||||
remote_port = 6001
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 53
|
||||
remote_port = 6002
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
use_encryption = false
|
||||
use_compression = true
|
||||
# http username and password are safety certification for http protocol
|
||||
# if not set, you can access this custom_domains without certification
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
# locations is only useful for http type
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6003
|
||||
# if plugin is defined, local_ip and local_port is useless
|
||||
# plugin will handle connections got from frps
|
||||
plugin = unix_domain_socket
|
||||
# params set with prefix "plugin_" that plugin needed
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6004
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
@@ -1,2 +0,0 @@
|
||||
[common]
|
||||
bind_port = 7000
|
||||
154
conf/frps.toml
Normal file
@@ -0,0 +1,154 @@
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single "bind_addr" field, no need square brackets, like "bind_addr = ::".
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = 7000
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||
# if not set, kcp is disabled in frps.
|
||||
kcpBindPort = 7000
|
||||
|
||||
# udp port used for quic protocol.
|
||||
# if not set, quic is disabled in frps.
|
||||
# quicBindPort = 7002
|
||||
|
||||
# Specify which address proxy will listen for, default value is same with bind_addr
|
||||
# proxy_bind_addr = "127.0.0.1"
|
||||
|
||||
# quic protocol options
|
||||
# transport.quic.keepalivePeriod = 10
|
||||
# transport.quic.maxIdleTimeout = 30
|
||||
# transport.quic.maxIncomingStreams = 100000
|
||||
|
||||
# Heartbeat configure, it's not recommended to modify the default value
|
||||
# The default value of heartbeat_timeout is 90. Set negative value to disable it.
|
||||
# transport.heartbeatTimeout = 90
|
||||
|
||||
# Pool count in each proxy will keep no more than maxPoolCount.
|
||||
transport.maxPoolCount = 5
|
||||
|
||||
# If tcp stream multiplexing is used, default is true
|
||||
# transport.tcpMux = true
|
||||
|
||||
# Specify keep alive interval for tcp mux.
|
||||
# only valid if tcpMux is true.
|
||||
# transport.tcpMuxKeepaliveInterval = 60
|
||||
|
||||
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# transport.tcpKeepalive = 7200
|
||||
|
||||
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||
tls.force = false
|
||||
|
||||
# transport.tls.certFile = "server.crt"
|
||||
# transport.tls.keyFile = "server.key"
|
||||
# transport.tls.trustedCaFile = "ca.crt"
|
||||
|
||||
# If you want to support virtual host, you must set the http port for listening (optional)
|
||||
# Note: http port and https port can be same with bind_port
|
||||
vhostHTTPPort = 80
|
||||
vhostHTTPSPort = 443
|
||||
|
||||
# Response header timeout(seconds) for vhost http server, default is 60s
|
||||
# vhostHTTPTimeout = 60
|
||||
|
||||
# tcpmuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
# requests on one single port. If it's not - it will listen on this value for
|
||||
# HTTP CONNECT requests. By default, this value is 0.
|
||||
# tcpmuxHTTPConnectPort = 1337
|
||||
|
||||
# If tcpmux_passthrough is true, frps won't do any update on traffic.
|
||||
# tcpmuxPassthrough = false
|
||||
|
||||
# Configure the web server to enable the dashboard for frps.
|
||||
# dashboard is available only if webServer.port is set.
|
||||
webServer.addr = "127.0.0.1"
|
||||
webServer.port = 7500
|
||||
webServer.user = "admin"
|
||||
webServer.password = "admin"
|
||||
# webServer.tls.certFile = "server.crt"
|
||||
# webServer.tls.keyFile = "server.key"
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# webServer.assetsDir = "./static"
|
||||
|
||||
# Enable golang pprof handlers in dashboard listener.
|
||||
# Dashboard port must be set first
|
||||
webServer.pprofEnable = false
|
||||
|
||||
# enablePrometheus will export prometheus metrics on webServer in /metrics api.
|
||||
enablePrometheus = true
|
||||
|
||||
# console or real logFile path like ./frps.log
|
||||
log.to = "./frps.log"
|
||||
# trace, debug, info, warn, error
|
||||
log.level = "info"
|
||||
log.maxDays = 3
|
||||
# disable log colors when log.to is console, default is false
|
||||
log.disablePrintColor = false
|
||||
|
||||
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||
detailedErrorsToClient = true
|
||||
|
||||
# auth.method specifies what authentication method to use authenticate frpc with frps.
|
||||
# If "token" is specified - token will be read into login message.
|
||||
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||
auth.method = "token"
|
||||
|
||||
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||
# Optional values are HeartBeats, NewWorkConns.
|
||||
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||
|
||||
# auth token
|
||||
auth.token = "12345678"
|
||||
|
||||
# oidc issuer specifies the issuer to verify OIDC tokens with.
|
||||
auth.oidc.issuer = ""
|
||||
# oidc audience specifies the audience OIDC tokens should contain when validated.
|
||||
auth.oidc.audience = ""
|
||||
# oidc skipExpiryCheck specifies whether to skip checking if the OIDC token is expired.
|
||||
auth.oidc.skipExpiryCheck = false
|
||||
# oidc skipIssuerCheck specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||
auth.oidc.skipIssuerCheck = false
|
||||
|
||||
# userConnTimeout specifies the maximum time to wait for a work connection.
|
||||
# userConnTimeout = 10
|
||||
|
||||
# Only allow frpc to bind ports you list. By default, there won't be any limit.
|
||||
allowPorts = [
|
||||
{ start = 2000, end = 3000 },
|
||||
{ single = 3001 },
|
||||
{ single = 3003 },
|
||||
{ start = 4000, end = 50000 }
|
||||
]
|
||||
|
||||
# Max ports can be used for each client, default value is 0 means no limit
|
||||
maxPortsPerClient = 0
|
||||
|
||||
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||
# When subdomain is est, the host used by routing is test.frps.com
|
||||
subDomainHost = "frps.com"
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom404Page = "/path/to/404.html"
|
||||
|
||||
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udpPacketSize = 1500
|
||||
|
||||
# Retention time for NAT hole punching strategy data.
|
||||
natholeAnalysisDataReserveHours = 168
|
||||
|
||||
[[httpPlugins]]
|
||||
name = "user-manager"
|
||||
addr = "127.0.0.1:9000"
|
||||
path = "/handler"
|
||||
ops = ["Login"]
|
||||
|
||||
[[httpPlugins]]
|
||||
name = "port-manager"
|
||||
addr = "127.0.0.1:9001"
|
||||
path = "/handler"
|
||||
ops = ["NewProxy"]
|
||||
@@ -1,55 +0,0 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'
|
||||
# if not set, kcp is disabled in frps
|
||||
kcp_bind_port = 7000
|
||||
|
||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||
vhost_http_port = 80
|
||||
vhost_https_port = 443
|
||||
|
||||
# if you want to configure or reload frps by dashboard, dashboard_port must be set
|
||||
dashboard_port = 7500
|
||||
|
||||
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# assets_dir = ./static
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = ./frps.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# privilege mode is the only supported mode since v0.10.0
|
||||
privilege_token = 12345678
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_timeout is 90
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
|
||||
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
|
||||
max_pool_count = 5
|
||||
|
||||
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
|
||||
# if authentication_timeout is zero, the time is not verified, default is 900s
|
||||
authentication_timeout = 900
|
||||
|
||||
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||
# when subdomain is test, the host used by routing is test.frps.com
|
||||
subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
389
conf/legacy/frpc_legacy_full.ini
Normal file
@@ -0,0 +1,389 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single "server_addr" field, no need square brackets, like "server_addr = ::".
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# STUN server to help penetrate NAT hole.
|
||||
# nat_hole_stun_server = stun.easyvoip.com:3478
|
||||
|
||||
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||
# dial_server_timeout = 10
|
||||
|
||||
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# dial_server_keepalive = 7200
|
||||
|
||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
# http_proxy = socks5://user:passwd@192.168.1.128:1080
|
||||
# http_proxy = ntlm://user:passwd@192.168.1.128:2080
|
||||
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = ./frpc.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# for authentication, should be same as your frps.ini
|
||||
# authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||
authenticate_heartbeats = false
|
||||
|
||||
# authenticate_new_work_conns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||
authenticate_new_work_conns = false
|
||||
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
authentication_method =
|
||||
|
||||
# oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||
# By default, this value is "".
|
||||
oidc_client_id =
|
||||
|
||||
# oidc_client_secret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||
# By default, this value is "".
|
||||
oidc_client_secret =
|
||||
|
||||
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_audience =
|
||||
|
||||
# oidc_scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_scope =
|
||||
|
||||
# oidc_token_endpoint_url specifies the URL which implements OIDC Token Endpoint.
|
||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_token_endpoint_url =
|
||||
|
||||
# oidc_additional_xxx specifies additional parameters to be sent to the OIDC Token Endpoint.
|
||||
# For example, if you want to specify the "audience" parameter, you can set as follow.
|
||||
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
|
||||
# oidc_additional_audience = https://dev.auth.com/api/v2/
|
||||
# oidc_additional_var1 = foobar
|
||||
|
||||
# set admin address for control frpc's action by http api such as reload
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||
# assets_dir = ./static
|
||||
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 5
|
||||
|
||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
|
||||
# your proxy name will be changed to {user}.{proxy}
|
||||
user = your_name
|
||||
|
||||
# decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||
# default is true
|
||||
login_fail_exit = true
|
||||
|
||||
# communication protocol used to connect to server
|
||||
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# set client binding ip when connect server, default is empty.
|
||||
# only when protocol = tcp or websocket, the value will be used.
|
||||
connect_server_local_ip = 0.0.0.0
|
||||
|
||||
# quic protocol options
|
||||
# quic_keepalive_period = 10
|
||||
# quic_max_idle_timeout = 30
|
||||
# quic_max_incoming_streams = 100000
|
||||
|
||||
# If tls_enable is true, frpc will connect frps by tls.
|
||||
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||
tls_enable = true
|
||||
|
||||
# tls_cert_file = client.crt
|
||||
# tls_key_file = client.key
|
||||
# tls_trusted_ca_file = ca.crt
|
||||
# tls_server_name = example.com
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start separated by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# The default value of heartbeat_interval is 10 and heartbeat_timeout is 90. Set negative value
|
||||
# to disable it.
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# additional meta info for client
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# include other config files for proxies.
|
||||
# includes = ./confd/*.ini
|
||||
|
||||
# If the disable_custom_tls_first_byte is set to false, frpc will establish a connection with frps using the
|
||||
# first custom byte when tls is enabled.
|
||||
# Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||
disable_custom_tls_first_byte = true
|
||||
|
||||
# Enable golang pprof handlers in admin listener.
|
||||
# Admin port must be set first.
|
||||
pprof_enable = false
|
||||
|
||||
# 'ssh' is the unique proxy name
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||
[ssh]
|
||||
# tcp | udp | http | https | stcp | xtcp, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# limit bandwidth for this proxy, unit is KB and MB
|
||||
bandwidth_limit = 1MB
|
||||
# where to limit bandwidth, can be 'client' or 'server', default is 'client'
|
||||
bandwidth_limit_mode = client
|
||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||
use_encryption = false
|
||||
# if true, message will be compressed
|
||||
use_compression = false
|
||||
# remote port listen by frps
|
||||
remote_port = 6001
|
||||
# frps will load balancing connections for proxies in same group
|
||||
group = test_group
|
||||
# group should have same group key
|
||||
group_key = 123456
|
||||
# enable health check for the backend service, it support 'tcp' and 'http' now
|
||||
# frpc will connect local service's port to detect it's healthy status
|
||||
health_check_type = tcp
|
||||
# health check connection timeout
|
||||
health_check_timeout_s = 3
|
||||
# if continuous failed in 3 times, the proxy will be removed from frps
|
||||
health_check_max_failed = 3
|
||||
# every 10 seconds will do a health check
|
||||
health_check_interval_s = 10
|
||||
# additional meta info for each proxy
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# if remote_port is 0, frps will assign a random port for you
|
||||
remote_port = 0
|
||||
|
||||
# if you want to expose multiple ports, add 'range:' prefix to the section name
|
||||
# frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on.
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6010-6020,6022,6024-6028
|
||||
remote_port = 6010-6020,6022,6024-6028
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 53
|
||||
remote_port = 6002
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 6010-6020
|
||||
remote_port = 6010-6020
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
use_encryption = false
|
||||
use_compression = true
|
||||
# http username and password are safety certification for http protocol
|
||||
# if not set, you can access this custom_domains without certification
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
|
||||
subdomain = web01
|
||||
custom_domains = web01.yourdomain.com
|
||||
# locations is only available for http type
|
||||
locations = /,/pic
|
||||
# route requests to this service if http basic auto user is abc
|
||||
# route_by_http_user = abc
|
||||
host_header_rewrite = example.com
|
||||
# params with prefix "header_" will be used to update http request headers
|
||||
header_X-From-Where = frp
|
||||
health_check_type = http
|
||||
# frpc will send a GET http request '/status' to local http service
|
||||
# http service is alive when it return 2xx http response code
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 10
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web02
|
||||
custom_domains = web02.yourdomain.com
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
proxy_protocol_version = v2
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6003
|
||||
# if plugin is defined, local_ip and local_port is useless
|
||||
# plugin will handle connections got from frps
|
||||
plugin = unix_domain_socket
|
||||
# params with prefix "plugin_" that plugin needed
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6004
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_socks5]
|
||||
type = tcp
|
||||
remote_port = 6005
|
||||
plugin = socks5
|
||||
plugin_user = abc
|
||||
plugin_passwd = abc
|
||||
|
||||
[plugin_static_file]
|
||||
type = tcp
|
||||
remote_port = 6006
|
||||
plugin = static_file
|
||||
plugin_local_path = /var/www/blog
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_https2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_https2https]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_http2https]
|
||||
type = http
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = http2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[secret_tcp]
|
||||
# If the type is secret tcp, remote_port is useless
|
||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||
type = stcp
|
||||
# sk used for authentication for visitors
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
# If not empty, only visitors from specified users can connect.
|
||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||
allow_users = *
|
||||
|
||||
# user of frpc should be same in both stcp server and stcp visitor
|
||||
[secret_tcp_visitor]
|
||||
# frpc role visitor -> frps -> frpc role server
|
||||
role = visitor
|
||||
type = stcp
|
||||
# the server name you want to visitor
|
||||
server_name = secret_tcp
|
||||
sk = abcdefg
|
||||
# connect this address to visitor stcp server
|
||||
bind_addr = 127.0.0.1
|
||||
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
# other visitors. (This is not supported for SUDP now)
|
||||
bind_port = 9000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[p2p_tcp]
|
||||
type = xtcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
# If not empty, only visitors from specified users can connect.
|
||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||
allow_users = user1, user2
|
||||
|
||||
[p2p_tcp_visitor]
|
||||
role = visitor
|
||||
type = xtcp
|
||||
# if the server user is not set, it defaults to the current user
|
||||
server_user = user1
|
||||
server_name = p2p_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
# other visitors. (This is not supported for SUDP now)
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
# when automatic tunnel persistence is required, set it to true
|
||||
keep_tunnel_open = false
|
||||
# effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour
|
||||
max_retries_an_hour = 8
|
||||
min_retry_interval = 90
|
||||
# fallback_to = stcp_visitor
|
||||
# fallback_timeout_ms = 500
|
||||
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
# route_by_http_user = user1
|
||||
168
conf/legacy/frps_legacy_full.ini
Normal file
@@ -0,0 +1,168 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
# For single "bind_addr" field, no need square brackets, like "bind_addr = ::".
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||
# if not set, kcp is disabled in frps.
|
||||
kcp_bind_port = 7000
|
||||
|
||||
# udp port used for quic protocol.
|
||||
# if not set, quic is disabled in frps.
|
||||
# quic_bind_port = 7002
|
||||
# quic protocol options
|
||||
# quic_keepalive_period = 10
|
||||
# quic_max_idle_timeout = 30
|
||||
# quic_max_incoming_streams = 100000
|
||||
|
||||
# specify which address proxy will listen for, default value is same with bind_addr
|
||||
# proxy_bind_addr = 127.0.0.1
|
||||
|
||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||
# Note: http port and https port can be same with bind_port
|
||||
vhost_http_port = 80
|
||||
vhost_https_port = 443
|
||||
|
||||
# response header timeout(seconds) for vhost http server, default is 60s
|
||||
# vhost_http_timeout = 60
|
||||
|
||||
# tcpmux_httpconnect_port specifies the port that the server listens for TCP
|
||||
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
# requests on one single port. If it's not - it will listen on this value for
|
||||
# HTTP CONNECT requests. By default, this value is 0.
|
||||
# tcpmux_httpconnect_port = 1337
|
||||
|
||||
# If tcpmux_passthrough is true, frps won't do any update on traffic.
|
||||
# tcpmux_passthrough = false
|
||||
|
||||
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||
# dashboard_addr's default value is same with bind_addr
|
||||
# dashboard is available only if dashboard_port is set
|
||||
dashboard_addr = 0.0.0.0
|
||||
dashboard_port = 7500
|
||||
|
||||
# dashboard user and passwd for basic auth protect
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
|
||||
# dashboard TLS mode
|
||||
dashboard_tls_mode = false
|
||||
# dashboard_tls_cert_file = server.crt
|
||||
# dashboard_tls_key_file = server.key
|
||||
|
||||
# enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
|
||||
enable_prometheus = true
|
||||
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# assets_dir = ./static
|
||||
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = ./frps.log
|
||||
|
||||
# trace, debug, info, warn, error
|
||||
log_level = info
|
||||
|
||||
log_max_days = 3
|
||||
|
||||
# disable log colors when log_file is console, default is false
|
||||
disable_log_color = false
|
||||
|
||||
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||
detailed_errors_to_client = true
|
||||
|
||||
# authentication_method specifies what authentication method to use authenticate frpc with frps.
|
||||
# If "token" is specified - token will be read into login message.
|
||||
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||
authentication_method = token
|
||||
|
||||
# authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||
authenticate_heartbeats = false
|
||||
|
||||
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||
authenticate_new_work_conns = false
|
||||
|
||||
# auth token
|
||||
token = 12345678
|
||||
|
||||
# oidc_issuer specifies the issuer to verify OIDC tokens with.
|
||||
# By default, this value is "".
|
||||
oidc_issuer =
|
||||
|
||||
# oidc_audience specifies the audience OIDC tokens should contain when validated.
|
||||
# By default, this value is "".
|
||||
oidc_audience =
|
||||
|
||||
# oidc_skip_expiry_check specifies whether to skip checking if the OIDC token is expired.
|
||||
# By default, this value is false.
|
||||
oidc_skip_expiry_check = false
|
||||
|
||||
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||
# By default, this value is false.
|
||||
oidc_skip_issuer_check = false
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_timeout is 90. Set negative value to disable it.
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||
# the default value of user_conn_timeout is 10
|
||||
# user_conn_timeout = 10
|
||||
|
||||
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||
|
||||
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
|
||||
max_pool_count = 5
|
||||
|
||||
# max ports can be used for each client, default value is 0 means no limit
|
||||
max_ports_per_client = 0
|
||||
|
||||
# tls_only specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||
tls_only = false
|
||||
|
||||
# tls_cert_file = server.crt
|
||||
# tls_key_file = server.key
|
||||
# tls_trusted_ca_file = ca.crt
|
||||
|
||||
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||
# when subdomain is test, the host used by routing is test.frps.com
|
||||
subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
|
||||
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# tcp_keepalive = 7200
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
|
||||
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||
# This parameter should be same between client and server.
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# Enable golang pprof handlers in dashboard listener.
|
||||
# Dashboard port must be set first
|
||||
pprof_enable = false
|
||||
|
||||
# Retention time for NAT hole punching strategy data.
|
||||
nat_hole_analysis_data_reserve_hours = 168
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
ops = Login
|
||||
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9001
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
BIN
doc/pic/sponsor_asocks.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
doc/pic/sponsor_doppler.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_workos.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
doc/pic/zsxq.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -1,135 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
frp is easier to use compared with other similar projects.
|
||||
|
||||
We will use two simple demo to demonstrate how to use frp.
|
||||
|
||||
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
|
||||
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
|
||||
|
||||
### Download SourceCode
|
||||
|
||||
`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`.
|
||||
|
||||
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
|
||||
|
||||
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
|
||||
|
||||
### Compile
|
||||
|
||||
Enter the root directory and execute `make`, then wait until finished.
|
||||
|
||||
**bin** include all executable programs when **conf** include corresponding configuration files.
|
||||
|
||||
### Pre-requirement
|
||||
|
||||
* Go environment. Version of go >= 1.4.
|
||||
* Godep (if not exist, `go get` will be executed to download godep when compiling)
|
||||
|
||||
### Deploy
|
||||
|
||||
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
|
||||
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
|
||||
3. Modify all configuration files, details in next paragraph.
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
|
||||
|
||||
## Tcp port forwarding
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
# for accept connections from frpc
|
||||
bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# finally we connect to server A by this port
|
||||
listen_port = 6000
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
# server address of frps
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# ssh is proxy name same with configure in frps.ini
|
||||
[ssh]
|
||||
# local port which need to be transferred
|
||||
local_port = 22
|
||||
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## Http port forwarding and Custom domains binding
|
||||
|
||||
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
|
||||
|
||||
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
|
||||
|
||||
After that, you can visit your web pages in local server by custom domains.
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# if you want to support vhost, specify one port for http services
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
auth_token = 123
|
||||
# # if proxy type equals http, custom_domains must be set separated by commas
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
# custom domains are set in frps.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# encryption is optional, default is false
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
@@ -1,137 +0,0 @@
|
||||
# frp 使用文档
|
||||
|
||||
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
|
||||
|
||||
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。
|
||||
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务,**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
|
||||
|
||||
### 下载源码
|
||||
|
||||
推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。
|
||||
|
||||
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
|
||||
|
||||
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
|
||||
|
||||
### 编译
|
||||
|
||||
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
|
||||
|
||||
编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。
|
||||
|
||||
### 依赖
|
||||
|
||||
* go 1.4 以上版本
|
||||
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
|
||||
|
||||
### 部署
|
||||
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
|
||||
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
|
||||
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。
|
||||
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**({user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
|
||||
|
||||
## tcp 端口转发
|
||||
|
||||
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
# 用于接收 frpc 连接的端口
|
||||
bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# ssh 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# 最后将通过此端口访问后端服务
|
||||
listen_port = 6000
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
# frps 所在服务器绑定的IP地址
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# 用于身份验证
|
||||
auth_token = 123
|
||||
|
||||
# ssh 需要和 frps.ini 中配置一致
|
||||
[ssh]
|
||||
# 需要转发的本地端口
|
||||
local_port = 22
|
||||
# 启用加密,frpc与frps之间通信加密,默认为 false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## http 端口转发,自定义域名绑定
|
||||
|
||||
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口,则需要指定代理的类型为 http,并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
|
||||
|
||||
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
|
||||
|
||||
之后就可以通过自定义域名访问到本地的多个 web 服务。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# 如果需要支持http类型的代理则需要指定一个端口
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
# type 默认为 tcp,这里需要特别指定为 http
|
||||
type = http
|
||||
auth_token = 123
|
||||
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
|
||||
# 自定义域名在 frps.ini 中配置,方便做统一管理
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# 可选是否加密
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
265
doc/server_plugin.md
Normal file
@@ -0,0 +1,265 @@
|
||||
### Server Plugin
|
||||
|
||||
frp server plugin is aimed to extend frp's ability without modifying the Golang code.
|
||||
|
||||
An external server should run in a different process receiving RPC calls from frps.
|
||||
Before frps is doing some operations, it will send RPC requests to notify the external RPC server and act according to its response.
|
||||
|
||||
### RPC request
|
||||
|
||||
RPC requests are based on JSON over HTTP.
|
||||
|
||||
When a server plugin accepts an operation request, it can respond with three different responses:
|
||||
|
||||
* Reject operation and return a reason.
|
||||
* Allow operation and keep original content.
|
||||
* Allow operation and return modified content.
|
||||
|
||||
### Interface
|
||||
|
||||
HTTP path can be configured for each manage plugin in frps. We'll assume for this example that it's `/handler`.
|
||||
|
||||
A request to the RPC server will look like:
|
||||
|
||||
```
|
||||
POST /handler?version=0.1.0&op=Login
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"op": "Login",
|
||||
"content": {
|
||||
... // Operation info
|
||||
}
|
||||
}
|
||||
|
||||
Request Header:
|
||||
X-Frp-Reqid: for tracing
|
||||
```
|
||||
|
||||
The response can look like any of the following:
|
||||
|
||||
* Non-200 HTTP response status code (this will automatically tell frps that the request should fail)
|
||||
|
||||
* Reject operation:
|
||||
|
||||
```
|
||||
{
|
||||
"reject": true,
|
||||
"reject_reason": "invalid user"
|
||||
}
|
||||
```
|
||||
|
||||
* Allow operation and keep original content:
|
||||
|
||||
```
|
||||
{
|
||||
"reject": false,
|
||||
"unchange": true
|
||||
}
|
||||
```
|
||||
|
||||
* Allow operation and modify content
|
||||
|
||||
```
|
||||
{
|
||||
"unchange": "false",
|
||||
"content": {
|
||||
... // Replaced content
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Operation
|
||||
|
||||
Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
|
||||
#### Login
|
||||
|
||||
Client login operation
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"version": <string>,
|
||||
"hostname": <string>,
|
||||
"os": <string>,
|
||||
"arch": <string>,
|
||||
"user": <string>,
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string,
|
||||
"client_address": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewProxy
|
||||
|
||||
Create new proxy
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"use_encryption": <bool>,
|
||||
"use_compression": <bool>,
|
||||
"bandwidth_limit": <string>,
|
||||
"bandwidth_limit_mode": <string>,
|
||||
"group": <string>,
|
||||
"group_key": <string>,
|
||||
|
||||
// tcp and udp only
|
||||
"remote_port": <int>,
|
||||
|
||||
// http and https only
|
||||
"custom_domains": []<string>,
|
||||
"subdomain": <string>,
|
||||
"locations": <string>,
|
||||
"http_user": <string>,
|
||||
"http_pwd": <string>,
|
||||
"host_header_rewrite": <string>,
|
||||
"headers": map<string>string,
|
||||
|
||||
// stcp only
|
||||
"sk": <string>,
|
||||
|
||||
// tcpmux only
|
||||
"multiplexer": <string>
|
||||
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CloseProxy
|
||||
|
||||
A previously created proxy is closed.
|
||||
|
||||
Please note that one request will be sent for every proxy that is closed, do **NOT** use this
|
||||
if you have too many proxies bound to a single client, as this may exhaust the server's resources.
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ping
|
||||
|
||||
Heartbeat from frpc
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewWorkConn
|
||||
|
||||
New work connection received from frpc (RPC sent after `run_id` is matched with an existing frp connection)
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"run_id": <string>
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewUserConn
|
||||
|
||||
New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcpmux`) .
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"remote_addr": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server Plugin Configuration
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
bindPort = 7000
|
||||
|
||||
[[httpPlugins]]
|
||||
name = "user-manager"
|
||||
addr = "127.0.0.1:9000"
|
||||
path = "/handler"
|
||||
ops = ["Login"]
|
||||
|
||||
[[httpPlugins]]
|
||||
name = "port-manager"
|
||||
addr = "127.0.0.1:9001"
|
||||
path = "/handler"
|
||||
ops = ["NewProxy"]
|
||||
```
|
||||
|
||||
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = "https://127.0.0.1:9001"`.
|
||||
- path: http request url path for the POST request.
|
||||
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
||||
- tlsVerify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
||||
|
||||
### Metadata
|
||||
|
||||
Metadata will be sent to the server plugin in each RPC request.
|
||||
|
||||
There are 2 types of metadata entries - global one and the other under each proxy configuration.
|
||||
Global metadata entries will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
||||
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
||||
|
||||
This is an example of metadata entries:
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
serverAddr = "127.0.0.1"
|
||||
serverPort = 7000
|
||||
user = "fake"
|
||||
metadatas.token = "fake"
|
||||
metadatas.version = "1.0.0"
|
||||
|
||||
[[proxies]]
|
||||
name = "ssh"
|
||||
type = "tcp"
|
||||
localPort = 22
|
||||
remotePort = 6000
|
||||
metadatas.id = "123"
|
||||
```
|
||||
12
dockerfiles/Dockerfile-for-frpc
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM golang:1.21 AS building
|
||||
|
||||
COPY . /building
|
||||
WORKDIR /building
|
||||
|
||||
RUN make frpc
|
||||
|
||||
FROM alpine:3
|
||||
|
||||
COPY --from=building /building/bin/frpc /usr/bin/frpc
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frpc"]
|
||||
12
dockerfiles/Dockerfile-for-frps
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM golang:1.21 AS building
|
||||
|
||||
COPY . /building
|
||||
WORKDIR /building
|
||||
|
||||
RUN make frps
|
||||
|
||||
FROM alpine:3
|
||||
|
||||
COPY --from=building /building/bin/frps /usr/bin/frps
|
||||
|
||||
ENTRYPOINT ["/usr/bin/frps"]
|
||||
83
go.mod
Normal file
@@ -0,0 +1,83 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/coreos/go-oidc/v3 v3.6.0
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/onsi/ginkgo/v2 v2.11.0
|
||||
github.com/onsi/gomega v1.27.8
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/pion/stun v0.6.1
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/quic-go/quic-go v0.37.4
|
||||
github.com/rodaine/table v1.1.0
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/time v0.3.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
k8s.io/apimachinery v0.27.4
|
||||
k8s.io/client-go v0.27.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.
|
||||
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d
|
||||
284
go.sum
Normal file
@@ -0,0 +1,284 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40 h1:BVdpWT6viE/mpuRa6txNyRNjtHa1Efrii9Du6/gHfJ0=
|
||||
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40/go.mod h1:Lmi9U4VfvdRvonSMh1FgXVy1hCXycVyJk4E9ktokknE=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
|
||||
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
|
||||
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
||||
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
|
||||
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
|
||||
k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
|
||||
k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
|
||||
k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
63
hack/download.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
OS="$(go env GOOS)"
|
||||
ARCH="$(go env GOARCH)"
|
||||
|
||||
if [ "${TARGET_OS}" ]; then
|
||||
OS="${TARGET_OS}"
|
||||
fi
|
||||
if [ "${TARGET_ARCH}" ]; then
|
||||
ARCH="${TARGET_ARCH}"
|
||||
fi
|
||||
|
||||
# Determine the latest version by version number ignoring alpha, beta, and rc versions.
|
||||
if [ "${FRP_VERSION}" = "" ] ; then
|
||||
FRP_VERSION="$(curl -sL https://github.com/fatedier/frp/releases | \
|
||||
grep -o 'releases/tag/v[0-9]*.[0-9]*.[0-9]*"' | sort -V | \
|
||||
tail -1 | awk -F'/' '{ print $3}')"
|
||||
FRP_VERSION="${FRP_VERSION%?}"
|
||||
FRP_VERSION="${FRP_VERSION#?}"
|
||||
fi
|
||||
|
||||
if [ "${FRP_VERSION}" = "" ] ; then
|
||||
printf "Unable to get latest frp version. Set FRP_VERSION env var and re-run. For example: export FRP_VERSION=1.0.0"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
SUFFIX=".tar.gz"
|
||||
if [ "${OS}" = "windows" ] ; then
|
||||
SUFFIX=".zip"
|
||||
fi
|
||||
NAME="frp_${FRP_VERSION}_${OS}_${ARCH}${SUFFIX}"
|
||||
DIR_NAME="frp_${FRP_VERSION}_${OS}_${ARCH}"
|
||||
URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${NAME}"
|
||||
|
||||
download_and_extract() {
|
||||
printf "Downloading %s from %s ...\n" "$NAME" "${URL}"
|
||||
if ! curl -o /dev/null -sIf "${URL}"; then
|
||||
printf "\n%s is not found, please specify a valid FRP_VERSION\n" "${URL}"
|
||||
exit 1
|
||||
fi
|
||||
curl -fsLO "${URL}"
|
||||
filename=$NAME
|
||||
|
||||
if [ "${OS}" = "windows" ]; then
|
||||
unzip "${filename}"
|
||||
else
|
||||
tar -xzf "${filename}"
|
||||
fi
|
||||
rm "${filename}"
|
||||
|
||||
if [ "${TARGET_DIRNAME}" ]; then
|
||||
mv "${DIR_NAME}" "${TARGET_DIRNAME}"
|
||||
DIR_NAME="${TARGET_DIRNAME}"
|
||||
fi
|
||||
}
|
||||
|
||||
download_and_extract
|
||||
|
||||
printf ""
|
||||
printf "\nfrp %s Download Complete!\n" "$FRP_VERSION"
|
||||
printf "\n"
|
||||
printf "frp has been successfully downloaded into the %s folder on your system.\n" "$DIR_NAME"
|
||||
printf "\n"
|
||||
30
hack/run-e2e.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
||||
|
||||
ginkgo_command=$(which ginkgo 2>/dev/null)
|
||||
if [ -z "$ginkgo_command" ]; then
|
||||
echo "ginkgo not found, try to install..."
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.11.0
|
||||
fi
|
||||
|
||||
debug=false
|
||||
if [ "x${DEBUG}" = "xtrue" ]; then
|
||||
debug=true
|
||||
fi
|
||||
logLevel=debug
|
||||
if [ "${LOG_LEVEL}" ]; then
|
||||
logLevel="${LOG_LEVEL}"
|
||||
fi
|
||||
|
||||
frpcPath=${ROOT}/bin/frpc
|
||||
if [ "${FRPC_PATH}" ]; then
|
||||
frpcPath="${FRPC_PATH}"
|
||||
fi
|
||||
frpsPath=${ROOT}/bin/frps
|
||||
if [ "${FRPS_PATH}" ]; then
|
||||
frpsPath="${FRPS_PATH}"
|
||||
fi
|
||||
|
||||
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
||||
@@ -1,200 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var ClientCommonCfg *ClientCommonConf
|
||||
|
||||
// client common config
|
||||
type ClientCommonConf struct {
|
||||
ConfigFile string
|
||||
ServerAddr string
|
||||
ServerPort int64
|
||||
HttpProxy string
|
||||
LogFile string
|
||||
LogWay string
|
||||
LogLevel string
|
||||
LogMaxDays int64
|
||||
PrivilegeToken string
|
||||
PoolCount int
|
||||
TcpMux bool
|
||||
User string
|
||||
LoginFailExit bool
|
||||
Start map[string]struct{}
|
||||
Protocol string
|
||||
HeartBeatInterval int64
|
||||
HeartBeatTimeout int64
|
||||
}
|
||||
|
||||
func GetDeaultClientCommonConf() *ClientCommonConf {
|
||||
return &ClientCommonConf{
|
||||
ConfigFile: "./frpc.ini",
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HttpProxy: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
PrivilegeToken: "",
|
||||
PoolCount: 1,
|
||||
TcpMux: true,
|
||||
User: "",
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
HeartBeatInterval: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
}
|
||||
}
|
||||
|
||||
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
cfg = GetDeaultClientCommonConf()
|
||||
|
||||
tmpStr, ok = conf.Get("common", "server_addr")
|
||||
if ok {
|
||||
cfg.ServerAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "server_port")
|
||||
if ok {
|
||||
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "http_proxy")
|
||||
if ok {
|
||||
cfg.HttpProxy = tmpStr
|
||||
} else {
|
||||
// get http_proxy from env
|
||||
cfg.HttpProxy = os.Getenv("http_proxy")
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_file")
|
||||
if ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_level")
|
||||
if ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "privilege_token")
|
||||
if ok {
|
||||
cfg.PrivilegeToken = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "pool_count")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
cfg.PoolCount = 1
|
||||
} else {
|
||||
cfg.PoolCount = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "user")
|
||||
if ok {
|
||||
cfg.User = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "start")
|
||||
if ok {
|
||||
proxyNames := strings.Split(tmpStr, ",")
|
||||
for _, name := range proxyNames {
|
||||
cfg.Start[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "login_fail_exit")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.LoginFailExit = false
|
||||
} else {
|
||||
cfg.LoginFailExit = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "protocol")
|
||||
if ok {
|
||||
// Now it only support tcp and kcp.
|
||||
if tmpStr != "kcp" {
|
||||
tmpStr = "tcp"
|
||||
}
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_interval")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatInterval = v
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.HeartBeatInterval <= 0 {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,492 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/models/consts"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var proxyConfTypeMap map[string]reflect.Type
|
||||
|
||||
func init() {
|
||||
proxyConfTypeMap = make(map[string]reflect.Type)
|
||||
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
|
||||
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||
}
|
||||
|
||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||
// If proxyType isn't exist, return nil.
|
||||
func NewConfByType(proxyType string) ProxyConf {
|
||||
v, ok := proxyConfTypeMap[proxyType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(ProxyConf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ProxyConf interface {
|
||||
GetName() string
|
||||
GetBaseInfo() *BaseProxyConf
|
||||
LoadFromMsg(pMsg *msg.NewProxy)
|
||||
LoadFromFile(name string, conf ini.Section) error
|
||||
UnMarshalToMsg(pMsg *msg.NewProxy)
|
||||
Check() error
|
||||
}
|
||||
|
||||
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||
if pMsg.ProxyType == "" {
|
||||
pMsg.ProxyType = consts.TcpProxy
|
||||
}
|
||||
|
||||
cfg = NewConfByType(pMsg.ProxyType)
|
||||
if cfg == nil {
|
||||
err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
|
||||
return
|
||||
}
|
||||
cfg.LoadFromMsg(pMsg)
|
||||
err = cfg.Check()
|
||||
return
|
||||
}
|
||||
|
||||
func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) {
|
||||
proxyType := section["type"]
|
||||
if proxyType == "" {
|
||||
proxyType = consts.TcpProxy
|
||||
section["type"] = consts.TcpProxy
|
||||
}
|
||||
cfg = NewConfByType(proxyType)
|
||||
if cfg == nil {
|
||||
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
|
||||
return
|
||||
}
|
||||
err = cfg.LoadFromFile(name, section)
|
||||
return
|
||||
}
|
||||
|
||||
// BaseProxy info
|
||||
type BaseProxyConf struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) GetName() string {
|
||||
return cfg.ProxyName
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.ProxyName = pMsg.ProxyName
|
||||
cfg.ProxyType = pMsg.ProxyType
|
||||
cfg.UseEncryption = pMsg.UseEncryption
|
||||
cfg.UseCompression = pMsg.UseCompression
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
if ClientCommonCfg.User != "" {
|
||||
cfg.ProxyName = ClientCommonCfg.User + "." + name
|
||||
} else {
|
||||
cfg.ProxyName = name
|
||||
}
|
||||
cfg.ProxyType = section["type"]
|
||||
|
||||
tmpStr, ok = section["use_encryption"]
|
||||
if ok && tmpStr == "true" {
|
||||
cfg.UseEncryption = true
|
||||
}
|
||||
|
||||
tmpStr, ok = section["use_compression"]
|
||||
if ok && tmpStr == "true" {
|
||||
cfg.UseCompression = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.ProxyName = cfg.ProxyName
|
||||
pMsg.ProxyType = cfg.ProxyType
|
||||
pMsg.UseEncryption = cfg.UseEncryption
|
||||
pMsg.UseCompression = cfg.UseCompression
|
||||
}
|
||||
|
||||
// Bind info
|
||||
type BindInfoConf struct {
|
||||
BindAddr string `json:"bind_addr"`
|
||||
RemotePort int64 `json:"remote_port"`
|
||||
}
|
||||
|
||||
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BindAddr = ServerCommonCfg.BindAddr
|
||||
cfg.RemotePort = pMsg.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
if tmpStr, ok = section["remote_port"]; ok {
|
||||
if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.RemotePort = cfg.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *BindInfoConf) check() (err error) {
|
||||
if len(ServerCommonCfg.PrivilegeAllowPorts) != 0 {
|
||||
if ok := util.ContainsPort(ServerCommonCfg.PrivilegeAllowPorts, cfg.RemotePort); !ok {
|
||||
return fmt.Errorf("remote port [%d] isn't allowed", cfg.RemotePort)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Domain info
|
||||
type DomainConf struct {
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
SubDomain string `json:"sub_domain"`
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.CustomDomains = pMsg.CustomDomains
|
||||
cfg.SubDomain = pMsg.SubDomain
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
if tmpStr, ok = section["custom_domains"]; ok {
|
||||
cfg.CustomDomains = strings.Split(tmpStr, ",")
|
||||
for i, domain := range cfg.CustomDomains {
|
||||
cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
}
|
||||
|
||||
if tmpStr, ok = section["subdomain"]; ok {
|
||||
cfg.SubDomain = tmpStr
|
||||
}
|
||||
|
||||
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
pMsg.CustomDomains = cfg.CustomDomains
|
||||
pMsg.SubDomain = cfg.SubDomain
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) check() (err error) {
|
||||
for _, domain := range cfg.CustomDomains {
|
||||
if ServerCommonCfg.SubDomainHost != "" && len(strings.Split(ServerCommonCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, ServerCommonCfg.SubDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, ServerCommonCfg.SubDomainHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.SubDomain != "" {
|
||||
if ServerCommonCfg.SubDomainHost == "" {
|
||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
|
||||
}
|
||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||
return fmt.Errorf("'.' and '*' is not supported in subdomain")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Local service info
|
||||
type LocalSvrConf struct {
|
||||
LocalIp string `json:"-"`
|
||||
LocalPort int `json:"-"`
|
||||
}
|
||||
|
||||
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
|
||||
cfg.LocalIp = "127.0.0.1"
|
||||
}
|
||||
|
||||
if tmpStr, ok := section["local_port"]; ok {
|
||||
if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PluginConf struct {
|
||||
Plugin string `json:"-"`
|
||||
PluginParams map[string]string `json:"-"`
|
||||
}
|
||||
|
||||
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
cfg.Plugin = section["plugin"]
|
||||
cfg.PluginParams = make(map[string]string)
|
||||
if cfg.Plugin != "" {
|
||||
// get params begin with "plugin_"
|
||||
for k, v := range section {
|
||||
if strings.HasPrefix(k, "plugin_") {
|
||||
cfg.PluginParams[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] no plugin info found", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TcpProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
|
||||
LocalSvrConf
|
||||
PluginConf
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *TcpProxyConf) Check() (err error) {
|
||||
err = cfg.BindInfoConf.check()
|
||||
return
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxyConf struct {
|
||||
BaseProxyConf
|
||||
BindInfoConf
|
||||
|
||||
LocalSvrConf
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *UdpProxyConf) Check() (err error) {
|
||||
err = cfg.BindInfoConf.check()
|
||||
return
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
LocalSvrConf
|
||||
PluginConf
|
||||
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
HttpUser string `json:"-"`
|
||||
HttpPwd string `json:"-"`
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||
cfg.DomainConf.LoadFromMsg(pMsg)
|
||||
|
||||
cfg.Locations = pMsg.Locations
|
||||
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
|
||||
cfg.HttpUser = pMsg.HttpUser
|
||||
cfg.HttpPwd = pMsg.HttpPwd
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
)
|
||||
if tmpStr, ok = section["locations"]; ok {
|
||||
cfg.Locations = strings.Split(tmpStr, ",")
|
||||
} else {
|
||||
cfg.Locations = []string{""}
|
||||
}
|
||||
|
||||
cfg.HostHeaderRewrite = section["host_header_rewrite"]
|
||||
cfg.HttpUser = section["http_user"]
|
||||
cfg.HttpPwd = section["http_pwd"]
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||
cfg.DomainConf.UnMarshalToMsg(pMsg)
|
||||
|
||||
pMsg.Locations = cfg.Locations
|
||||
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
|
||||
pMsg.HttpUser = cfg.HttpUser
|
||||
pMsg.HttpPwd = cfg.HttpPwd
|
||||
}
|
||||
|
||||
func (cfg *HttpProxyConf) Check() (err error) {
|
||||
if ServerCommonCfg.VhostHttpPort == 0 {
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
err = cfg.DomainConf.check()
|
||||
return
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxyConf struct {
|
||||
BaseProxyConf
|
||||
DomainConf
|
||||
|
||||
LocalSvrConf
|
||||
PluginConf
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||
cfg.DomainConf.LoadFromMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||
cfg.DomainConf.UnMarshalToMsg(pMsg)
|
||||
}
|
||||
|
||||
func (cfg *HttpsProxyConf) Check() (err error) {
|
||||
if ServerCommonCfg.VhostHttpsPort == 0 {
|
||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||
}
|
||||
err = cfg.DomainConf.check()
|
||||
return
|
||||
}
|
||||
|
||||
// if len(startProxy) is 0, start all
|
||||
// otherwise just start proxies in startProxy map
|
||||
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (proxyConfs map[string]ProxyConf, err error) {
|
||||
if prefix != "" {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
startAll := true
|
||||
if len(startProxy) > 0 {
|
||||
startAll = false
|
||||
}
|
||||
proxyConfs = make(map[string]ProxyConf)
|
||||
for name, section := range conf {
|
||||
_, shouldStart := startProxy[name]
|
||||
if name != "common" && (startAll || shouldStart) {
|
||||
cfg, err := NewProxyConfFromFile(name, section)
|
||||
if err != nil {
|
||||
return proxyConfs, err
|
||||
}
|
||||
proxyConfs[prefix+name] = cfg
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/utils/util"
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var ServerCommonCfg *ServerCommonConf
|
||||
|
||||
// common config
|
||||
type ServerCommonConf struct {
|
||||
ConfigFile string
|
||||
BindAddr string
|
||||
BindPort int64
|
||||
KcpBindPort int64
|
||||
|
||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||
VhostHttpPort int64
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
VhostHttpsPort int64
|
||||
|
||||
// if DashboardPort equals 0, dashboard is not available
|
||||
DashboardPort int64
|
||||
DashboardUser string
|
||||
DashboardPwd string
|
||||
AssetsDir string
|
||||
LogFile string
|
||||
LogWay string // console or file
|
||||
LogLevel string
|
||||
LogMaxDays int64
|
||||
PrivilegeMode bool
|
||||
PrivilegeToken string
|
||||
AuthTimeout int64
|
||||
SubDomainHost string
|
||||
TcpMux bool
|
||||
|
||||
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
|
||||
PrivilegeAllowPorts [][2]int64
|
||||
MaxPoolCount int64
|
||||
HeartBeatTimeout int64
|
||||
UserConnTimeout int64
|
||||
}
|
||||
|
||||
func GetDefaultServerCommonConf() *ServerCommonConf {
|
||||
return &ServerCommonConf{
|
||||
ConfigFile: "./frps.ini",
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
KcpBindPort: 0,
|
||||
VhostHttpPort: 0,
|
||||
VhostHttpsPort: 0,
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "admin",
|
||||
DashboardPwd: "admin",
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
PrivilegeMode: true,
|
||||
PrivilegeToken: "",
|
||||
AuthTimeout: 900,
|
||||
SubDomainHost: "",
|
||||
TcpMux: true,
|
||||
MaxPoolCount: 5,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
}
|
||||
}
|
||||
|
||||
// Load server common configure.
|
||||
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||
var (
|
||||
tmpStr string
|
||||
ok bool
|
||||
v int64
|
||||
)
|
||||
cfg = GetDefaultServerCommonConf()
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_addr")
|
||||
if ok {
|
||||
cfg.BindAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_port")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.BindPort = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "kcp_bind_port")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil && v > 0 {
|
||||
cfg.KcpBindPort = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "vhost_http_port")
|
||||
if ok {
|
||||
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
cfg.VhostHttpPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "vhost_https_port")
|
||||
if ok {
|
||||
cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
cfg.VhostHttpsPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_port")
|
||||
if ok {
|
||||
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
cfg.DashboardPort = 0
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_user")
|
||||
if ok {
|
||||
cfg.DashboardUser = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "dashboard_pwd")
|
||||
if ok {
|
||||
cfg.DashboardPwd = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "assets_dir")
|
||||
if ok {
|
||||
cfg.AssetsDir = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_file")
|
||||
if ok {
|
||||
cfg.LogFile = tmpStr
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_level")
|
||||
if ok {
|
||||
cfg.LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil {
|
||||
cfg.LogMaxDays = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "privilege_mode")
|
||||
if ok {
|
||||
if tmpStr == "true" {
|
||||
cfg.PrivilegeMode = true
|
||||
}
|
||||
}
|
||||
|
||||
// PrivilegeMode configure
|
||||
if cfg.PrivilegeMode == true {
|
||||
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
|
||||
|
||||
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
|
||||
// TODO: check if conflicts exist in port ranges
|
||||
if ok {
|
||||
cfg.PrivilegeAllowPorts, err = util.GetPortRanges(allowPortsStr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "max_pool_count")
|
||||
if ok {
|
||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||
if err == nil && v >= 0 {
|
||||
cfg.MaxPoolCount = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "authentication_timeout")
|
||||
if ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.AuthTimeout = v
|
||||
}
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "subdomain_host")
|
||||
if ok {
|
||||
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "tcp_mux")
|
||||
if ok && tmpStr == "false" {
|
||||
cfg.TcpMux = false
|
||||
} else {
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
||||
if ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||
return
|
||||
} else {
|
||||
cfg.HeartBeatTimeout = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeCloseProxy = 'c'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUdpPacket = 'u'
|
||||
)
|
||||
|
||||
var (
|
||||
TypeMap map[byte]reflect.Type
|
||||
TypeStringMap map[reflect.Type]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
TypeMap = make(map[byte]reflect.Type)
|
||||
TypeStringMap = make(map[reflect.Type]byte)
|
||||
|
||||
TypeMap[TypeLogin] = reflect.TypeOf(Login{})
|
||||
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
|
||||
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
|
||||
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
|
||||
TypeMap[TypeCloseProxy] = reflect.TypeOf(CloseProxy{})
|
||||
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
||||
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
||||
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
||||
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
||||
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
||||
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
||||
|
||||
for k, v := range TypeMap {
|
||||
TypeStringMap[v] = k
|
||||
}
|
||||
}
|
||||
|
||||
// Message wraps socket packages for communicating between frpc and frps.
|
||||
type Message interface{}
|
||||
|
||||
// When frpc start, client send this message to login to server.
|
||||
type Login struct {
|
||||
Version string `json:"version"`
|
||||
Hostname string `json:"hostname"`
|
||||
Os string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
User string `json:"user"`
|
||||
PrivilegeKey string `json:"privilege_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
RunId string `json:"run_id"`
|
||||
|
||||
// Some global configures.
|
||||
PoolCount int `json:"pool_count"`
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
Version string `json:"version"`
|
||||
RunId string `json:"run_id"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// When frpc login success, send this message to frps for running a new proxy.
|
||||
type NewProxy struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
ProxyType string `json:"proxy_type"`
|
||||
UseEncryption bool `json:"use_encryption"`
|
||||
UseCompression bool `json:"use_compression"`
|
||||
|
||||
// tcp and udp only
|
||||
RemotePort int64 `json:"remote_port"`
|
||||
|
||||
// http and https only
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
SubDomain string `json:"subdomain"`
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
HttpUser string `json:"http_user"`
|
||||
HttpPwd string `json:"http_pwd"`
|
||||
}
|
||||
|
||||
type NewProxyResp struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type CloseProxy struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
}
|
||||
|
||||
type NewWorkConn struct {
|
||||
RunId string `json:"run_id"`
|
||||
}
|
||||
|
||||
type ReqWorkConn struct {
|
||||
}
|
||||
|
||||
type StartWorkConn struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
}
|
||||
|
||||
type Ping struct {
|
||||
}
|
||||
|
||||
type Pong struct {
|
||||
}
|
||||
|
||||
type UdpPacket struct {
|
||||
Content string `json:"c"`
|
||||
LocalAddr *net.UDPAddr `json:"l"`
|
||||
RemoteAddr *net.UDPAddr `json:"r"`
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
)
|
||||
|
||||
func unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
|
||||
if msgIn == nil {
|
||||
t, ok := TypeMap[typeByte]
|
||||
if !ok {
|
||||
err = fmt.Errorf("Unsupported message type %b", typeByte)
|
||||
return
|
||||
}
|
||||
|
||||
msg = reflect.New(t).Interface().(Message)
|
||||
} else {
|
||||
msg = msgIn
|
||||
}
|
||||
|
||||
err = json.Unmarshal(buffer, &msg)
|
||||
return
|
||||
}
|
||||
|
||||
func UnPackInto(buffer []byte, msg Message) (err error) {
|
||||
_, err = unpack(' ', buffer, msg)
|
||||
return
|
||||
}
|
||||
|
||||
func UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
|
||||
return unpack(typeByte, buffer, nil)
|
||||
}
|
||||
|
||||
func Pack(msg Message) ([]byte, error) {
|
||||
typeByte, ok := TypeStringMap[reflect.TypeOf(msg).Elem()]
|
||||
if !ok {
|
||||
return nil, errors.ErrMsgType
|
||||
}
|
||||
|
||||
content, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteByte(typeByte)
|
||||
binary.Write(buffer, binary.BigEndian, int64(len(content)))
|
||||
buffer.Write(content)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/utils/errors"
|
||||
)
|
||||
|
||||
type TestStruct struct{}
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var (
|
||||
msg Message
|
||||
buffer []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// error type
|
||||
msg = &TestStruct{}
|
||||
buffer, err = Pack(msg)
|
||||
assert.Error(err, errors.ErrMsgType.Error())
|
||||
|
||||
// correct
|
||||
msg = &Ping{}
|
||||
buffer, err = Pack(msg)
|
||||
assert.NoError(err)
|
||||
b := bytes.NewBuffer(nil)
|
||||
b.WriteByte(TypePing)
|
||||
binary.Write(b, binary.BigEndian, int64(2))
|
||||
b.WriteString("{}")
|
||||
assert.True(bytes.Equal(b.Bytes(), buffer))
|
||||
}
|
||||
|
||||
func TestUnPack(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var (
|
||||
msg Message
|
||||
err error
|
||||
)
|
||||
|
||||
// error message type
|
||||
msg, err = UnPack('-', []byte("{}"))
|
||||
assert.Error(err)
|
||||
|
||||
// correct
|
||||
msg, err = UnPack(TypePong, []byte("{}"))
|
||||
assert.NoError(err)
|
||||
assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{}))
|
||||
}
|
||||
|
||||
func TestUnPackInto(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var err error
|
||||
|
||||
// correct type
|
||||
pongMsg := &Pong{}
|
||||
err = UnPackInto([]byte("{}"), pongMsg)
|
||||
assert.NoError(err)
|
||||
|
||||
// wrong type
|
||||
loginMsg := &Login{}
|
||||
err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
|
||||
assert.Error(err)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package msg
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
MaxMsgLength int64 = 10240
|
||||
)
|
||||
|
||||
func readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
|
||||
buffer = make([]byte, 1)
|
||||
_, err = c.Read(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
typeByte = buffer[0]
|
||||
if _, ok := TypeMap[typeByte]; !ok {
|
||||
err = fmt.Errorf("Message type error")
|
||||
return
|
||||
}
|
||||
|
||||
var length int64
|
||||
err = binary.Read(c, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if length > MaxMsgLength {
|
||||
err = fmt.Errorf("Message length exceed the limit")
|
||||
return
|
||||
}
|
||||
|
||||
buffer = make([]byte, length)
|
||||
n, err := io.ReadFull(c, buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if int64(n) != length {
|
||||
err = fmt.Errorf("Message format error")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadMsg(c io.Reader) (msg Message, err error) {
|
||||
typeByte, buffer, err := readMsg(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return UnPack(typeByte, buffer)
|
||||
}
|
||||
|
||||
func ReadMsgInto(c io.Reader, msg Message) (err error) {
|
||||
_, buffer, err := readMsg(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return UnPackInto(buffer, msg)
|
||||
}
|
||||
|
||||
func WriteMsg(c io.Writer, msg interface{}) (err error) {
|
||||
buffer, err := Pack(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = c.Write(buffer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||