mirror of
https://github.com/fatedier/frp.git
synced 2026-03-08 10:59:11 +08:00
Compare commits
505 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ad3cf9a64a | ||
|
|
e3fc73dbc5 | ||
|
|
f884e894f2 | ||
|
|
d57ed7d3d8 | ||
|
|
a2c318d24c | ||
|
|
32f8745d61 | ||
|
|
66120fe49d | ||
|
|
fca7f42b37 | ||
|
|
5b303f5148 | ||
|
|
2a044c9d6d | ||
|
|
70e2aee46d | ||
|
|
6742fa2ea8 | ||
|
|
511503d34c | ||
|
|
1eaf17fd05 | ||
|
|
04f4fd0a81 | ||
|
|
3a4d769bb3 | ||
|
|
84341b7fcc | ||
|
|
80ba931326 | ||
|
|
7ebcc7503a | ||
|
|
74cf57feb3 | ||
|
|
712afed0ab | ||
|
|
e29a1330ed | ||
|
|
44971c7918 | ||
|
|
7bc6c72844 | ||
|
|
93461e0094 | ||
|
|
03d55201b2 | ||
|
|
e6d82f3162 | ||
|
|
1af6276be9 | ||
|
|
d1f5ec083a | ||
|
|
716ec281f6 | ||
|
|
67bfae5d23 | ||
|
|
f0dc3ed47b | ||
|
|
08b0885564 | ||
|
|
49b503c17b | ||
|
|
150682ec63 | ||
|
|
4dc96f41c9 | ||
|
|
6c13b6d37a | ||
|
|
1c04de380d | ||
|
|
738e5dad22 | ||
|
|
6d81e4c8c6 | ||
|
|
faf584e1dd | ||
|
|
ba6afd5789 | ||
|
|
11260389a1 | ||
|
|
b8082e6e08 | ||
|
|
7957572ced | ||
|
|
c2ff37d0d8 | ||
|
|
c67f9d5e76 | ||
|
|
1cc61b60f9 | ||
|
|
9c38baeb9e | ||
|
|
84465a7463 | ||
|
|
3fe50df200 | ||
|
|
93d86ca635 | ||
|
|
b600a07ec0 | ||
|
|
a5f06489cb | ||
|
|
2883d70ea9 | ||
|
|
3f17837a2c | ||
|
|
fd268b5082 | ||
|
|
69b09eb8a2 | ||
|
|
a84dd05351 | ||
|
|
71f7caa1ee | ||
|
|
5360febd72 | ||
|
|
1b70f0c4fd | ||
|
|
5c75efa222 | ||
|
|
ab4a53965b | ||
|
|
a0c83bdb78 | ||
|
|
30aeaf968e | ||
|
|
58d0d41501 | ||
|
|
6a95a63fd4 | ||
|
|
aa185eb9f3 | ||
|
|
d8683a0079 | ||
|
|
8b2cde3a30 | ||
|
|
634e048d0c | ||
|
|
a4fece3f51 | ||
|
|
9e683fe446 | ||
|
|
54bbfe26b0 | ||
|
|
a1023fdfc2 | ||
|
|
b02e1007fb | ||
|
|
f90028cf96 | ||
|
|
f83a2a73ab | ||
|
|
307b74cc13 | ||
|
|
f00a28598f | ||
|
|
6ee0b25782 | ||
|
|
88083d21e8 | ||
|
|
a22440aade | ||
|
|
b006540141 | ||
|
|
e655f07674 | ||
|
|
aafa96db58 | ||
|
|
1325148cd3 | ||
|
|
3f9749488a | ||
|
|
f9a0d891a1 | ||
|
|
92daa45b68 | ||
|
|
5f20a22b0d | ||
|
|
63be94c611 | ||
|
|
694ee44af6 | ||
|
|
edb97abf50 | ||
|
|
0c10279deb | ||
|
|
1f49510e3e | ||
|
|
1868b3bafb | ||
|
|
a23521885c | ||
|
|
c80dcd050d | ||
|
|
043ab62587 | ||
|
|
a8969b1901 | ||
|
|
e26285eefc | ||
|
|
299bd7b5cb | ||
|
|
90d1384bf7 | ||
|
|
a5434e31b7 | ||
|
|
044bb692dc | ||
|
|
34b98dde52 | ||
|
|
020f786bf5 | ||
|
|
cdcc1240ec | ||
|
|
c2c9f68a00 | ||
|
|
37470c26f0 | ||
|
|
04a4591caa | ||
|
|
8bf61d5e39 | ||
|
|
659f84bab2 | ||
|
|
9faf4acd62 | ||
|
|
4c3fb22295 | ||
|
|
d243f70125 | ||
|
|
a56f068f8c | ||
|
|
6a6ccc5302 | ||
|
|
6f90c3400c | ||
|
|
eb4f779384 | ||
|
|
59a34b81e0 | ||
|
|
b1d1a7a20a | ||
|
|
6b34ed4644 | ||
|
|
a44be1e2ed | ||
|
|
a4c05e6ff9 | ||
|
|
d93dd82ed9 | ||
|
|
edf4bc431d |
@@ -1,5 +0,0 @@
|
|||||||
Dockerfile
|
|
||||||
.git
|
|
||||||
*~
|
|
||||||
*#
|
|
||||||
.#*
|
|
||||||
32
.github/ISSUE_TEMPLATE
vendored
Normal file
32
.github/ISSUE_TEMPLATE
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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 将会直接关闭。)
|
||||||
|
(请不要在 issue 评论中出现无意义的 **加1**,**我也是** 等内容,将会被直接删除。)
|
||||||
|
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
|
||||||
|
|
||||||
|
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)**
|
||||||
@@ -2,9 +2,7 @@ sudo: false
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.5.4
|
- 1.12.x
|
||||||
- 1.6.4
|
|
||||||
- 1.7.4
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|||||||
17
Dockerfile
17
Dockerfile
@@ -1,17 +0,0 @@
|
|||||||
FROM golang:1.6
|
|
||||||
|
|
||||||
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.4
|
|
||||||
|
|
||||||
COPY bin/frpc /frpc
|
|
||||||
COPY bin/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"]
|
|
||||||
44
Godeps/Godeps.json
generated
44
Godeps/Godeps.json
generated
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"ImportPath": "github.com/fatedier/frp",
|
|
||||||
"GoVersion": "go1.7",
|
|
||||||
"GodepVersion": "v75",
|
|
||||||
"Packages": [
|
|
||||||
"./..."
|
|
||||||
],
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/astaxie/beego/logs",
|
|
||||||
"Comment": "v1.7.0-7-gefbde1e",
|
|
||||||
"Rev": "efbde1ee77517486eac03e814e01d724ddad18e6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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/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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
5
Godeps/Readme
generated
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.
|
|
||||||
58
Makefile
58
Makefile
@@ -1,55 +1,43 @@
|
|||||||
export PATH := $(GOPATH)/bin:$(PATH)
|
export PATH := $(GOPATH)/bin:$(PATH)
|
||||||
export GO15VENDOREXPERIMENT := 1
|
|
||||||
|
|
||||||
all: fmt build
|
all: fmt build
|
||||||
|
|
||||||
build: frps frpc build_test
|
build: frps frpc
|
||||||
|
|
||||||
build_test: echo_server http_server
|
|
||||||
|
|
||||||
# compile assets into binary file
|
# compile assets into binary file
|
||||||
assets:
|
file:
|
||||||
go get -d github.com/rakyll/statik
|
rm -rf ./assets/frps/static/*
|
||||||
@go install github.com/rakyll/statik
|
rm -rf ./assets/frpc/static/*
|
||||||
@rm -rf ./src/assets/statik
|
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||||
go generate ./src/...
|
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||||
|
rm -rf ./assets/frps/statik
|
||||||
|
rm -rf ./assets/frpc/statik
|
||||||
|
go generate ./assets/...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./src/...
|
go fmt ./...
|
||||||
@go fmt ./test/echo_server.go
|
|
||||||
@go fmt ./test/http_server.go
|
|
||||||
@go fmt ./test/func_test.go
|
|
||||||
|
|
||||||
frps:
|
frps:
|
||||||
go build -o bin/frps ./src/cmd/frps
|
go build -o bin/frps ./cmd/frps
|
||||||
@cp -rf ./src/assets/static ./bin
|
|
||||||
|
|
||||||
frpc:
|
frpc:
|
||||||
go build -o bin/frpc ./src/cmd/frpc
|
go build -o bin/frpc ./cmd/frpc
|
||||||
|
|
||||||
echo_server:
|
|
||||||
go build -o test/bin/echo_server ./test/echo_server.go
|
|
||||||
|
|
||||||
http_server:
|
|
||||||
go build -o test/bin/http_server ./test/http_server.go
|
|
||||||
|
|
||||||
test: gotest
|
test: gotest
|
||||||
|
|
||||||
gotest:
|
gotest:
|
||||||
go test -v ./src/...
|
go test -v --cover ./assets/...
|
||||||
|
go test -v --cover ./client/...
|
||||||
|
go test -v --cover ./cmd/...
|
||||||
|
go test -v --cover ./models/...
|
||||||
|
go test -v --cover ./server/...
|
||||||
|
go test -v --cover ./utils/...
|
||||||
|
|
||||||
alltest:
|
ci:
|
||||||
cd ./test && ./run_test.sh && cd -
|
go test -count=1 -p=1 -v ./tests/...
|
||||||
go test -v ./src/...
|
|
||||||
go test -v ./test/func_test.go
|
|
||||||
cd ./test && ./clean_test.sh && cd -
|
|
||||||
|
|
||||||
|
alltest: gotest ci
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ./bin/frpc
|
rm -f ./bin/frpc
|
||||||
rm -f ./bin/frps
|
rm -f ./bin/frps
|
||||||
rm -f ./test/bin/echo_server
|
|
||||||
rm -f ./test/bin/http_server
|
|
||||||
cd ./test && ./clean_test.sh && cd -
|
|
||||||
|
|
||||||
save:
|
|
||||||
godep save ./src/...
|
|
||||||
|
|||||||
@@ -1,18 +1,37 @@
|
|||||||
export PATH := $(GOPATH)/bin:$(PATH)
|
export PATH := $(GOPATH)/bin:$(PATH)
|
||||||
export GO15VENDOREXPERIMENT := 1
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build: gox app more
|
build: app
|
||||||
|
|
||||||
gox:
|
|
||||||
go get github.com/mitchellh/gox
|
|
||||||
|
|
||||||
app:
|
app:
|
||||||
gox -osarch "darwin/386 darwin/amd64 linux/386 linux/amd64 linux/arm windows/386 windows/amd64" ./src/...
|
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=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
|
||||||
|
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
|
||||||
|
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
|
||||||
|
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_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=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./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 GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
|
||||||
|
|
||||||
more:
|
temp:
|
||||||
env GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc
|
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
|
||||||
env GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps
|
|
||||||
env GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc
|
|
||||||
env GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./src/cmd/frps
|
|
||||||
|
|||||||
694
README.md
694
README.md
@@ -1,50 +1,67 @@
|
|||||||
# frp
|
# frp
|
||||||
|
|
||||||
[](https://travis-ci.org/fatedier/frp)
|
[](https://travis-ci.org/fatedier/frp)
|
||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
|
|
||||||
## What is frp?
|
## What is frp?
|
||||||
|
|
||||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, http and https protocol when requests can be forwarded by domains to backward web services.
|
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
|
||||||
|
|
||||||
## Catalog
|
Now it also try to support p2p connect.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
|
||||||
* [Status](#status)
|
* [Status](#status)
|
||||||
* [Architecture](#architecture)
|
* [Architecture](#architecture)
|
||||||
* [Example Usage](#example-usage)
|
* [Example Usage](#example-usage)
|
||||||
* [Communicate with your computer in LAN by SSH](#communicate-with-your-computer-in-lan-by-ssh)
|
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
|
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||||
|
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||||
|
* [Expose your service in security](#expose-your-service-in-security)
|
||||||
|
* [P2P Mode](#p2p-mode)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
|
* [Configuration File](#configuration-file)
|
||||||
|
* [Configuration file template](#configuration-file-template)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
|
* [Admin UI](#admin-ui)
|
||||||
* [Authentication](#authentication)
|
* [Authentication](#authentication)
|
||||||
* [Encryption and Compression](#encryption-and-compression)
|
* [Encryption and Compression](#encryption-and-compression)
|
||||||
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
|
* [TLS](#tls)
|
||||||
* [Privilege Mode](#privilege-mode)
|
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||||
* [Port White List](#port-white-list)
|
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||||
|
* [Port White List](#port-white-list)
|
||||||
|
* [Port Reuse](#port-reuse)
|
||||||
|
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||||
|
* [Support KCP Protocol](#support-kcp-protocol)
|
||||||
* [Connection Pool](#connection-pool)
|
* [Connection Pool](#connection-pool)
|
||||||
|
* [Load balancing](#load-balancing)
|
||||||
|
* [Health Check](#health-check)
|
||||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||||
|
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
||||||
|
* [Get Real IP](#get-real-ip)
|
||||||
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||||
* [Custom subdomain names](#custom-subdomain-names)
|
* [Custom subdomain names](#custom-subdomain-names)
|
||||||
|
* [URL routing](#url-routing)
|
||||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
||||||
|
* [Range ports mapping](#range-ports-mapping)
|
||||||
|
* [Plugin](#plugin)
|
||||||
* [Development Plan](#development-plan)
|
* [Development Plan](#development-plan)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
* [AliPay](#alipay)
|
* [AliPay](#alipay)
|
||||||
|
* [Wechat Pay](#wechat-pay)
|
||||||
* [Paypal](#paypal)
|
* [Paypal](#paypal)
|
||||||
* [Contributors](#contributors)
|
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
|
|
||||||
## What can I do with frp?
|
|
||||||
|
|
||||||
* Expose any http and https service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
|
|
||||||
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
|
|
||||||
* Inspect all http requests/responses that are transmitted over the tunnel(future).
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
|
||||||
@@ -63,37 +80,33 @@ Put **frps** and **frps.ini** to your server with public IP.
|
|||||||
|
|
||||||
Put **frpc** and **frpc.ini** to your server in LAN.
|
Put **frpc** and **frpc.ini** to your server in LAN.
|
||||||
|
|
||||||
### Communicate with your computer in LAN by SSH
|
### Access your computer in LAN by SSH
|
||||||
|
|
||||||
1. Modify frps.ini, configure a reverse proxy named [ssh]:
|
1. Modify frps.ini:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
[ssh]
|
|
||||||
listen_port = 6000
|
|
||||||
auth_token = 123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start frps:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
`./frps -c ./frps.ini`
|
||||||
|
|
||||||
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x:
|
3. Modify frpc.ini, `server_addr` is your frps's server IP:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
auth_token = 123
|
|
||||||
|
|
||||||
[ssh]
|
[ssh]
|
||||||
type = tcp
|
type = tcp
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 22
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
4. Start frpc:
|
||||||
@@ -108,20 +121,15 @@ Put **frpc** and **frpc.ini** to your server in LAN.
|
|||||||
|
|
||||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
||||||
|
|
||||||
Howerver, we can expose a http or https service using frp.
|
However, we can expose a http or https service using frp.
|
||||||
|
|
||||||
1. Modify frps.ini, configure a http reverse proxy named [web] and set http port as 8080, custom domain as `www.yourdomain.com`:
|
1. Modify frps.ini, configure http port 8080:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
vhost_http_port = 8080
|
vhost_http_port = 8080
|
||||||
|
|
||||||
[web]
|
|
||||||
type = http
|
|
||||||
custom_domains = www.yourdomain.com
|
|
||||||
auth_token = 123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start frps:
|
||||||
@@ -135,11 +143,11 @@ Howerver, we can expose a http or https service using frp.
|
|||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
auth_token = 123
|
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
|
custom_domains = www.yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
4. Start frpc:
|
||||||
@@ -152,17 +160,12 @@ Howerver, we can expose a http or https service using frp.
|
|||||||
|
|
||||||
### Forward DNS query request
|
### Forward DNS query request
|
||||||
|
|
||||||
1. Modify frps.ini, configure a reverse proxy named [dns]:
|
1. Modify frps.ini:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
[dns]
|
|
||||||
type = udp
|
|
||||||
listen_port = 6000
|
|
||||||
auth_token = 123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Start frps:
|
||||||
@@ -176,12 +179,12 @@ Howerver, we can expose a http or https service using frp.
|
|||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
auth_token = 123
|
|
||||||
|
|
||||||
[dns]
|
[dns]
|
||||||
type = udp
|
type = udp
|
||||||
local_ip = 8.8.8.8
|
local_ip = 8.8.8.8
|
||||||
local_port = 53
|
local_port = 53
|
||||||
|
remote_port = 6000
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
4. Start frpc:
|
||||||
@@ -190,10 +193,212 @@ Howerver, we can expose a http or https service using frp.
|
|||||||
|
|
||||||
5. Send dns query request by dig:
|
5. Send dns query request by dig:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
|
### Forward unix domain socket
|
||||||
|
|
||||||
|
Using tcp port to connect unix domain socket like docker daemon.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Get docker version by curl command:
|
||||||
|
|
||||||
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
|
### Expose a simple http file server
|
||||||
|
|
||||||
|
A simple way to visit files in the LAN.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_static_file]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = /tmp/file
|
||||||
|
plugin_strip_prefix = static
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||||
|
|
||||||
|
### Enable HTTPS for local HTTP service
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_htts2http]
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit `https://test.yourdomain.com`.
|
||||||
|
|
||||||
|
### Expose your service in security
|
||||||
|
|
||||||
|
For some services, if expose them to the public network directly will be a security risk.
|
||||||
|
|
||||||
|
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc, forward ssh port and `remote_port` is useless:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh]
|
||||||
|
type = stcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start another frpc in which you want to connect this ssh server:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh_visitor]
|
||||||
|
type = stcp
|
||||||
|
role = visitor
|
||||||
|
server_name = secret_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Connect to server in LAN by ssh assuming that username is test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### P2P Mode
|
||||||
|
|
||||||
|
**xtcp** is designed for transmitting a large amount of data directly between two client.
|
||||||
|
|
||||||
|
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
|
||||||
|
|
||||||
|
1. Configure a udp port for xtcp:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
bind_udp_port = 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start frpc, forward ssh port and `remote_port` is useless:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh]
|
||||||
|
type = xtcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start another frpc in which you want to connect this ssh server:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh_visitor]
|
||||||
|
type = xtcp
|
||||||
|
role = visitor
|
||||||
|
server_name = p2p_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Connect to server in LAN by ssh assuming that username is test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### Configuration File
|
||||||
|
|
||||||
|
You can find features which this document not metioned from full example configuration files.
|
||||||
|
|
||||||
|
[frps full configuration file](./conf/frps_full.ini)
|
||||||
|
|
||||||
|
[frpc full configuration file](./conf/frpc_full.ini)
|
||||||
|
|
||||||
|
### Configuration file template
|
||||||
|
|
||||||
|
Configuration file tempalte can be rendered using os environments. Template uses Go's standard format.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = {{ .Envs.FRP_SERVER_ADDR }}
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[ssh]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Start frpc program:
|
||||||
|
|
||||||
|
```
|
||||||
|
export FRP_SERVER_ADDR="x.x.x.x"
|
||||||
|
export FRP_SSH_REMOTE_PORT="6000"
|
||||||
|
./frpc -c ./frpc.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
frpc will auto render configuration file template using os environments.
|
||||||
|
All environments has prefix `.Envs`.
|
||||||
|
|
||||||
### Dashboard
|
### Dashboard
|
||||||
|
|
||||||
Check frp's status and proxies's statistics information by Dashboard.
|
Check frp's status and proxies's statistics information by Dashboard.
|
||||||
@@ -212,123 +417,124 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Admin UI
|
||||||
|
|
||||||
|
Admin UI help you check and manage frpc's configure.
|
||||||
|
|
||||||
|
Configure a address for admin UI to enable this feature:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
`auth_token` in frps.ini is configured for each proxy and check for authentication when frpc login in.
|
`token` in frps.ini and frpc.ini should be same.
|
||||||
|
|
||||||
Client that want's to register must set a global `auth_token` equals to frps.ini.
|
|
||||||
|
|
||||||
Note that time duration bewtween frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
|
|
||||||
|
|
||||||
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
|
|
||||||
|
|
||||||
### Encryption and Compression
|
### Encryption and Compression
|
||||||
|
|
||||||
Defalut value is false, you could decide if the proxy will use encryption or compression whether the type is:
|
Defalut value is false, you could decide if the proxy will use encryption or compression:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[ssh]
|
[ssh]
|
||||||
type = tcp
|
type = tcp
|
||||||
listen_port = 6000
|
local_port = 22
|
||||||
auth_token = 123
|
remote_port = 6000
|
||||||
use_encryption = true
|
use_encryption = true
|
||||||
use_gzip = true
|
use_compression = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reload configures without frps stopped
|
#### TLS
|
||||||
|
|
||||||
If your want to add a new reverse proxy and avoid restarting frps, you can use this function:
|
frp support TLS protocol between frpc and frps since v0.25.0.
|
||||||
|
|
||||||
1. `dashboard_port` should be set in frps.ini:
|
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
||||||
|
|
||||||
|
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
||||||
|
|
||||||
|
### Hot-Reload frpc configuration
|
||||||
|
|
||||||
|
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
|
||||||
|
|
||||||
|
**Note that parameters in [common] section won't be modified except 'start' now.**
|
||||||
|
|
||||||
|
### Get proxy status from client
|
||||||
|
|
||||||
|
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
|
||||||
|
|
||||||
|
### Port White List
|
||||||
|
|
||||||
|
`allow_ports` in frps.ini is used for preventing abuse of ports:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||||
|
```
|
||||||
|
|
||||||
|
`allow_ports` consists of a specific port or a range of ports divided by `,`.
|
||||||
|
|
||||||
|
### Port Reuse
|
||||||
|
|
||||||
|
Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
|
||||||
|
|
||||||
|
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
||||||
|
|
||||||
|
### TCP Stream Multiplexing
|
||||||
|
|
||||||
|
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
|
||||||
|
|
||||||
|
You can disable this feature by modify frps.ini and frpc.ini:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini and frpc.ini, must be same
|
||||||
|
[common]
|
||||||
|
tcp_mux = false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Support KCP Protocol
|
||||||
|
|
||||||
|
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||||
|
|
||||||
|
Using kcp in frp:
|
||||||
|
|
||||||
|
1. Enable kcp protocol in frps:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
dashboard_port = 7500
|
# kcp needs to bind a udp port, it can be same with 'bind_port'
|
||||||
|
kcp_bind_port = 7000
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start frps:
|
2. Configure the protocol used in frpc to connect frps:
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
|
||||||
|
|
||||||
3. Modify frps.ini to add a new proxy [new_ssh]:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# frps.ini
|
|
||||||
[common]
|
|
||||||
bind_port = 7000
|
|
||||||
dashboard_port = 7500
|
|
||||||
|
|
||||||
[new_ssh]
|
|
||||||
listen_port = 6001
|
|
||||||
auth_token = 123
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Execute `reload` command:
|
|
||||||
|
|
||||||
`./frps -c ./frps.ini --reload`
|
|
||||||
|
|
||||||
5. Start frpc and [new_ssh] is available now.
|
|
||||||
|
|
||||||
### Privilege Mode
|
|
||||||
|
|
||||||
Privilege mode is used for who don't want to do operations in frps everytime adding a new proxy.
|
|
||||||
|
|
||||||
All proxies's configurations are set in frpc.ini when privilege mode is enabled.
|
|
||||||
|
|
||||||
1. Enable privilege mode and set `privilege_token`.Client with the same `privilege_token` can create proxy automaticly:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# frps.ini
|
|
||||||
[common]
|
|
||||||
bind_port = 7000
|
|
||||||
privilege_mode = true
|
|
||||||
privilege_token = 1234
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start frps:
|
|
||||||
|
|
||||||
`./frps -c ./frps.ini`
|
|
||||||
|
|
||||||
3. Enable privilege mode for proxy [ssh]:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
|
# specify the 'kcp_bind_port' in frps
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
privilege_token = 1234
|
protocol = kcp
|
||||||
|
|
||||||
[ssh]
|
|
||||||
privilege_mode = true
|
|
||||||
local_port = 22
|
|
||||||
remote_port = 6000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start frpc:
|
|
||||||
|
|
||||||
`./frpc -c ./frpc.ini`
|
|
||||||
|
|
||||||
5. Connect to server in LAN by ssh assuming username is test:
|
|
||||||
|
|
||||||
`ssh -oPort=6000 test@x.x.x.x`
|
|
||||||
|
|
||||||
#### Port White List
|
|
||||||
|
|
||||||
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports in privilege mode:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# frps.ini
|
|
||||||
[common]
|
|
||||||
privilege_mode = true
|
|
||||||
privilege_token = 1234
|
|
||||||
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
|
||||||
```
|
|
||||||
|
|
||||||
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
|
|
||||||
|
|
||||||
### Connection Pool
|
### Connection Pool
|
||||||
|
|
||||||
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
||||||
@@ -337,37 +543,157 @@ This feature is fit for a large number of short connections.
|
|||||||
|
|
||||||
1. Configure the limit of pool count each proxy can use in frps.ini:
|
1. Configure the limit of pool count each proxy can use in frps.ini:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
[common]
|
[common]
|
||||||
max_pool_count = 50
|
max_pool_count = 5
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Enable and specify the number of connection pool:
|
2. Enable and specify the number of connection pool:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[ssh]
|
[common]
|
||||||
type = tcp
|
pool_count = 1
|
||||||
local_port = 22
|
|
||||||
pool_count = 10
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Load balancing
|
||||||
|
|
||||||
|
Load balancing is supported by `group`.
|
||||||
|
|
||||||
|
This feature is available only for type `tcp` and `http` now.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[test1]
|
||||||
|
type = tcp
|
||||||
|
local_port = 8080
|
||||||
|
remote_port = 80
|
||||||
|
group = web
|
||||||
|
group_key = 123
|
||||||
|
|
||||||
|
[test2]
|
||||||
|
type = tcp
|
||||||
|
local_port = 8081
|
||||||
|
remote_port = 80
|
||||||
|
group = web
|
||||||
|
group_key = 123
|
||||||
|
```
|
||||||
|
|
||||||
|
`group_key` is used for authentication.
|
||||||
|
|
||||||
|
Proxies in same group will accept connections from port 80 randomly.
|
||||||
|
|
||||||
|
For `tcp` type, `remote_port` in one group shoud be same.
|
||||||
|
|
||||||
|
For `http` type, `custom_domains, subdomain, locations` shoud be same.
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
Health check feature can help you achieve high availability with load balancing.
|
||||||
|
|
||||||
|
Add `health_check_type = {type}` to enable health check.
|
||||||
|
|
||||||
|
**type** can be tcp or http.
|
||||||
|
|
||||||
|
Type tcp will dial the service port and type http will send a http rquest to service and require a 200 response.
|
||||||
|
|
||||||
|
Type tcp configuration:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[test1]
|
||||||
|
type = tcp
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
# enable tcp health check
|
||||||
|
health_check_type = tcp
|
||||||
|
# dial timeout seconds
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Type http configuration:
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 80
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
# enable http health check
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
### Rewriting the Host Header
|
### Rewriting the Host Header
|
||||||
|
|
||||||
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified Host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
|
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
privilege_mode = true
|
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.yourdomain.com
|
||||||
host_header_rewrite = dev.yourdomain.com
|
host_header_rewrite = dev.yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
||||||
|
|
||||||
|
### Set Headers In HTTP Request
|
||||||
|
|
||||||
|
You can set headers for proxy which type is `http`.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = http
|
||||||
|
local_port = 80
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
host_header_rewrite = dev.yourdomain.com
|
||||||
|
header_X-From-Where = frp
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that params which have prefix `header_` will be added to http request headers.
|
||||||
|
In this example, it will set header `X-From-Where: frp` to http request.
|
||||||
|
|
||||||
|
### Get Real IP
|
||||||
|
|
||||||
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
|
Features for http proxy only.
|
||||||
|
|
||||||
|
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
||||||
|
|
||||||
|
#### Proxy Protocol
|
||||||
|
|
||||||
|
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
||||||
|
|
||||||
|
Here is an example for https service:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = https
|
||||||
|
local_port = 443
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
# now v1 and v2 is supported
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
```
|
||||||
|
|
||||||
|
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
||||||
|
|
||||||
|
Then you can get it from HTTP request header in your local service.
|
||||||
|
|
||||||
### Password protecting your web service
|
### Password protecting your web service
|
||||||
|
|
||||||
@@ -375,12 +701,11 @@ Anyone who can guess your tunnel URL can access your local web server unless you
|
|||||||
|
|
||||||
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
|
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
|
||||||
|
|
||||||
It can be only enabled when proxy type is http.
|
It can only be enabled when proxy type is http.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
privilege_mode = true
|
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
custom_domains = test.yourdomain.com
|
custom_domains = test.yourdomain.com
|
||||||
@@ -388,7 +713,7 @@ http_user = abc
|
|||||||
http_pwd = abc
|
http_pwd = abc
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit `test.yourdomain.com` and now you need to input username and password.
|
Visit `http://test.yourdomain.com` and now you need to input username and password.
|
||||||
|
|
||||||
### Custom subdomain names
|
### Custom subdomain names
|
||||||
|
|
||||||
@@ -404,7 +729,6 @@ Resolve `*.frps.com` to the frps server's IP.
|
|||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[web]
|
[web]
|
||||||
privilege_mode = true
|
|
||||||
type = http
|
type = http
|
||||||
local_port = 80
|
local_port = 80
|
||||||
subdomain = test
|
subdomain = test
|
||||||
@@ -414,28 +738,82 @@ Now you can visit your web service by host `test.frps.com`.
|
|||||||
|
|
||||||
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
|
||||||
|
|
||||||
|
### URL routing
|
||||||
|
|
||||||
|
frp support forward http requests to different backward web services by url routing.
|
||||||
|
|
||||||
|
`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
|
||||||
|
|
||||||
### Connect frps by HTTP PROXY
|
### Connect frps by HTTP PROXY
|
||||||
|
|
||||||
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
||||||
|
|
||||||
|
It only works when protocol is tcp.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Range ports mapping
|
||||||
|
|
||||||
|
Proxy name has prefix `range:` will support mapping range ports.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[range:test_tcp]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 6000-6006,6007
|
||||||
|
remote_port = 6000-6006,6007
|
||||||
|
```
|
||||||
|
|
||||||
|
frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
|
||||||
|
|
||||||
|
### Plugin
|
||||||
|
|
||||||
|
frpc only forward request to local tcp or udp port by default.
|
||||||
|
|
||||||
|
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||||
|
|
||||||
|
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||||
|
|
||||||
|
Using plugin **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` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||||
|
|
||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* Url router.
|
|
||||||
* Log http request information in frps.
|
* Log http request information in frps.
|
||||||
* Direct reverse proxy, like haproxy.
|
|
||||||
* Load balance to different service in frpc.
|
|
||||||
* Debug mode for frpc, prestent proxy status in terminal.
|
|
||||||
* Inspect all http requests/responses that are transmitted over the tunnel.
|
|
||||||
* Frpc can directly be a webserver for static files.
|
|
||||||
* Full control mode, dynamically modify frpc's configure with dashboard in frps.
|
|
||||||
* P2p communicate by make udp hole to penetrate NAT.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -444,7 +822,7 @@ Interested in getting involved? We would like to help you!
|
|||||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
||||||
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
||||||
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
||||||
* If you have some wanderful ideas, send email to fatedier@gmail.com.
|
* If you have some wonderful ideas, send email to fatedier@gmail.com.
|
||||||
|
|
||||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
||||||
|
|
||||||
@@ -452,20 +830,16 @@ Interested in getting involved? We would like to help you!
|
|||||||
|
|
||||||
If frp help you a lot, you can support us by:
|
If frp help you a lot, you can support us by:
|
||||||
|
|
||||||
|
frp QQ group: 606194980
|
||||||
|
|
||||||
### AliPay
|
### AliPay
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Wechat Pay
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Paypal
|
### Paypal
|
||||||
|
|
||||||
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
* [fatedier](https://github.com/fatedier)
|
|
||||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
|
||||||
* [Pan Hao](https://github.com/vashstorm)
|
|
||||||
* [Danping Mao](https://github.com/maodanp)
|
|
||||||
* [Eric Larssen](https://github.com/ericlarssen)
|
|
||||||
* [Damon Zhao](https://github.com/se77en)
|
|
||||||
* [Manfred Touron](https://github.com/moul)
|
|
||||||
|
|||||||
745
README_zh.md
745
README_zh.md
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,10 @@
|
|||||||
|
|
||||||
package assets
|
package assets
|
||||||
|
|
||||||
//go:generate statik -src=./static
|
//go:generate statik -src=./frps/static -dest=./frps
|
||||||
//go:generate go fmt statik/statik.go
|
//go:generate statik -src=./frpc/static -dest=./frpc
|
||||||
|
//go:generate go fmt ./frps/statik/statik.go
|
||||||
|
//go:generate go fmt ./frpc/statik/statik.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -24,8 +26,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/src/assets/statik"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BIN
assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frpc/static/favicon.ico
Normal file
BIN
assets/frpc/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>
|
||||||
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=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(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"edb271e1d9c81f857840"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
||||||
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
1
assets/frps/static/index.html
Normal file
1
assets/frps/static/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!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?14bea8276eef86cc7c61"></script><script type="text/javascript" src="vendor.js?51925ec1a77936b64d61"></script></body> </html>
|
||||||
1
assets/frps/static/manifest.js
Normal file
1
assets/frps/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=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(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"51925ec1a77936b64d61"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
||||||
1
assets/frps/static/vendor.js
Normal file
1
assets/frps/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
72
client/admin.go
Normal file
72
client/admin.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// 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"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/assets"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpServerReadTimeout = 10 * time.Second
|
||||||
|
httpServerWriteTimeout = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||||
|
// url router
|
||||||
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
|
||||||
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
|
// api, see dashboard_api.go
|
||||||
|
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
|
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||||
|
|
||||||
|
// view
|
||||||
|
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
|
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
|
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 server.Serve(ln)
|
||||||
|
return
|
||||||
|
}
|
||||||
326
client/admin_api.go
Normal file
326
client/admin_api.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
// 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/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeneralResponse struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/reload
|
||||||
|
|
||||||
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http request [/api/reload]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [/api/reload], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("reload frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("reload frpc common section error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||||
|
if 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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusResp struct {
|
||||||
|
Tcp []ProxyStatusResp `json:"tcp"`
|
||||||
|
Udp []ProxyStatusResp `json:"udp"`
|
||||||
|
Http []ProxyStatusResp `json:"http"`
|
||||||
|
Https []ProxyStatusResp `json:"https"`
|
||||||
|
Stcp []ProxyStatusResp `json:"stcp"`
|
||||||
|
Xtcp []ProxyStatusResp `json:"xtcp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByProxyStatusResp []ProxyStatusResp
|
||||||
|
|
||||||
|
func (a ByProxyStatusResp) Len() int { return len(a) }
|
||||||
|
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||||
|
|
||||||
|
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||||
|
psr := ProxyStatusResp{
|
||||||
|
Name: status.Name,
|
||||||
|
Type: status.Type,
|
||||||
|
Status: status.Status,
|
||||||
|
Err: status.Err,
|
||||||
|
}
|
||||||
|
switch cfg := status.Cfg.(type) {
|
||||||
|
case *config.TcpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
|
if status.Err != "" {
|
||||||
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||||
|
} else {
|
||||||
|
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||||
|
}
|
||||||
|
case *config.UdpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
if status.Err != "" {
|
||||||
|
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
|
||||||
|
} else {
|
||||||
|
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
|
||||||
|
}
|
||||||
|
case *config.HttpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
|
case *config.HttpsProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
|
}
|
||||||
|
return psr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/status
|
||||||
|
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
res StatusResp
|
||||||
|
)
|
||||||
|
res.Tcp = make([]ProxyStatusResp, 0)
|
||||||
|
res.Udp = make([]ProxyStatusResp, 0)
|
||||||
|
res.Http = make([]ProxyStatusResp, 0)
|
||||||
|
res.Https = make([]ProxyStatusResp, 0)
|
||||||
|
res.Stcp = make([]ProxyStatusResp, 0)
|
||||||
|
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
switch status.Type {
|
||||||
|
case "tcp":
|
||||||
|
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
|
||||||
|
case "udp":
|
||||||
|
res.Udp = append(res.Udp, NewProxyStatusResp(status))
|
||||||
|
case "http":
|
||||||
|
res.Http = append(res.Http, NewProxyStatusResp(status))
|
||||||
|
case "https":
|
||||||
|
res.Https = append(res.Https, NewProxyStatusResp(status))
|
||||||
|
case "stcp":
|
||||||
|
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
|
||||||
|
case "xtcp":
|
||||||
|
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Udp))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Http))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Https))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Stcp))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/config
|
||||||
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *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 g.GlbClientCfg.CfgFile == "" {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "frpc has no config file path"
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := strings.Split(content, "\n")
|
||||||
|
newRows := make([]string, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
}
|
||||||
|
res.Msg = strings.Join(newRows, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := ioutil.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// get token from origin content
|
||||||
|
token := ""
|
||||||
|
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := string(b)
|
||||||
|
|
||||||
|
for _, row := range strings.Split(content, "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
token = row
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpRows := make([]string, 0)
|
||||||
|
for _, row := range strings.Split(string(body), "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpRows = append(tmpRows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRows := make([]string, 0)
|
||||||
|
if token != "" {
|
||||||
|
for _, row := range tmpRows {
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
if strings.HasPrefix(row, "[common]") {
|
||||||
|
newRows = append(newRows, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRows = tmpRows
|
||||||
|
}
|
||||||
|
content = strings.Join(newRows, "\n")
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 500
|
||||||
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
318
client/control.go
Normal file
318
client/control.go
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
|
"github.com/fatedier/golib/crypto"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Control struct {
|
||||||
|
// uniq id got from frps, attach it in loginMsg
|
||||||
|
runId string
|
||||||
|
|
||||||
|
// manage all proxies
|
||||||
|
pxyCfgs map[string]config.ProxyConf
|
||||||
|
pm *proxy.ProxyManager
|
||||||
|
|
||||||
|
// manage all visitors
|
||||||
|
vm *VisitorManager
|
||||||
|
|
||||||
|
// control connection
|
||||||
|
conn frpNet.Conn
|
||||||
|
|
||||||
|
// tcp stream multiplexing, if enabled
|
||||||
|
session *fmux.Session
|
||||||
|
|
||||||
|
// put a message in this channel to send it over control connection to server
|
||||||
|
sendCh chan (msg.Message)
|
||||||
|
|
||||||
|
// read from this channel to get the next message sent by server
|
||||||
|
readCh chan (msg.Message)
|
||||||
|
|
||||||
|
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
||||||
|
closedCh chan struct{}
|
||||||
|
|
||||||
|
closedDoneCh chan struct{}
|
||||||
|
|
||||||
|
// last time got the Pong message
|
||||||
|
lastPong time.Time
|
||||||
|
|
||||||
|
readerShutdown *shutdown.Shutdown
|
||||||
|
writerShutdown *shutdown.Shutdown
|
||||||
|
msgHandlerShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
|
||||||
|
ctl := &Control{
|
||||||
|
runId: runId,
|
||||||
|
conn: conn,
|
||||||
|
session: session,
|
||||||
|
pxyCfgs: pxyCfgs,
|
||||||
|
sendCh: make(chan msg.Message, 100),
|
||||||
|
readCh: make(chan msg.Message, 100),
|
||||||
|
closedCh: make(chan struct{}),
|
||||||
|
closedDoneCh: make(chan struct{}),
|
||||||
|
readerShutdown: shutdown.New(),
|
||||||
|
writerShutdown: shutdown.New(),
|
||||||
|
msgHandlerShutdown: shutdown.New(),
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
|
}
|
||||||
|
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
|
||||||
|
|
||||||
|
ctl.vm = NewVisitorManager(ctl)
|
||||||
|
ctl.vm.Reload(visitorCfgs)
|
||||||
|
return ctl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Run() {
|
||||||
|
go ctl.worker()
|
||||||
|
|
||||||
|
// start all proxies
|
||||||
|
ctl.pm.Reload(ctl.pxyCfgs)
|
||||||
|
|
||||||
|
// start all visitors
|
||||||
|
go ctl.vm.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||||
|
workConn, err := ctl.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &msg.NewWorkConn{
|
||||||
|
RunId: ctl.runId,
|
||||||
|
}
|
||||||
|
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||||
|
ctl.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)
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||||
|
|
||||||
|
// dispatch this work connection to related proxy
|
||||||
|
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||||
|
// 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 {
|
||||||
|
ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
|
||||||
|
} else {
|
||||||
|
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Close() error {
|
||||||
|
ctl.pm.Close()
|
||||||
|
ctl.conn.Close()
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosedDoneCh returns a channel which 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 frpNet.Conn, err error) {
|
||||||
|
if g.GlbClientCfg.TcpMux {
|
||||||
|
stream, errRet := ctl.session.OpenStream()
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
ctl.Warn("start new connection to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = frpNet.WrapConn(stream)
|
||||||
|
} else {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if g.GlbClientCfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
ctl.Warn("start new connection to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reader read all messages from frps and send to readCh
|
||||||
|
func (ctl *Control) reader() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.Error("panic error: %v", err)
|
||||||
|
ctl.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer ctl.readerShutdown.Done()
|
||||||
|
defer close(ctl.closedCh)
|
||||||
|
|
||||||
|
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||||
|
for {
|
||||||
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
ctl.Debug("read from control connection EOF")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ctl.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctl.readCh <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writer writes messages got from sendCh to frps
|
||||||
|
func (ctl *Control) writer() {
|
||||||
|
defer ctl.writerShutdown.Done()
|
||||||
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
ctl.conn.Error("crypto new writer error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if m, ok := <-ctl.sendCh; !ok {
|
||||||
|
ctl.Info("control writer is closing")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||||
|
ctl.Warn("write message to control connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// msgHandler handles all channel events and do corresponding operations.
|
||||||
|
func (ctl *Control) msgHandler() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.Error("panic error: %v", err)
|
||||||
|
ctl.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
|
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
|
||||||
|
defer hbSend.Stop()
|
||||||
|
hbCheck := time.NewTicker(time.Second)
|
||||||
|
defer hbCheck.Stop()
|
||||||
|
|
||||||
|
ctl.lastPong = time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-hbSend.C:
|
||||||
|
// send heartbeat to server
|
||||||
|
ctl.Debug("send heartbeat to server")
|
||||||
|
ctl.sendCh <- &msg.Ping{}
|
||||||
|
case <-hbCheck.C:
|
||||||
|
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
|
||||||
|
ctl.Warn("heartbeat timeout")
|
||||||
|
// let reader() stop
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case rawMsg, ok := <-ctl.readCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.ReqWorkConn:
|
||||||
|
go ctl.HandleReqWorkConn(m)
|
||||||
|
case *msg.NewProxyResp:
|
||||||
|
ctl.HandleNewProxyResp(m)
|
||||||
|
case *msg.Pong:
|
||||||
|
ctl.lastPong = time.Now()
|
||||||
|
ctl.Debug("receive heartbeat from server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctl.closedCh:
|
||||||
|
// close related channels and wait until other goroutines done
|
||||||
|
close(ctl.readCh)
|
||||||
|
ctl.readerShutdown.WaitDone()
|
||||||
|
ctl.msgHandlerShutdown.WaitDone()
|
||||||
|
|
||||||
|
close(ctl.sendCh)
|
||||||
|
ctl.writerShutdown.WaitDone()
|
||||||
|
|
||||||
|
ctl.pm.Close()
|
||||||
|
ctl.vm.Close()
|
||||||
|
|
||||||
|
close(ctl.closedDoneCh)
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
||||||
|
ctl.vm.Reload(visitorCfgs)
|
||||||
|
ctl.pm.Reload(pxyCfgs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
28
client/event/event.go
Normal file
28
client/event/event.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
EvStartProxy EventType = iota
|
||||||
|
EvCloseProxy
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPayloadType = errors.New("error payload type")
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventHandler func(evType EventType, payload interface{}) error
|
||||||
|
|
||||||
|
type StartProxyPayload struct {
|
||||||
|
NewProxyMsg *msg.NewProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseProxyPayload struct {
|
||||||
|
CloseProxyMsg *msg.CloseProxy
|
||||||
|
}
|
||||||
182
client/health/health.go
Normal file
182
client/health/health.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrHealthCheckType = errors.New("error health check type")
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthCheckMonitor 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
|
||||||
|
|
||||||
|
l log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
|
||||||
|
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
|
||||||
|
|
||||||
|
if intervalS <= 0 {
|
||||||
|
intervalS = 10
|
||||||
|
}
|
||||||
|
if timeoutS <= 0 {
|
||||||
|
timeoutS = 3
|
||||||
|
}
|
||||||
|
if maxFailedTimes <= 0 {
|
||||||
|
maxFailedTimes = 1
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &HealthCheckMonitor{
|
||||||
|
checkType: checkType,
|
||||||
|
interval: time.Duration(intervalS) * time.Second,
|
||||||
|
timeout: time.Duration(timeoutS) * time.Second,
|
||||||
|
maxFailedTimes: maxFailedTimes,
|
||||||
|
addr: addr,
|
||||||
|
url: url,
|
||||||
|
statusOK: false,
|
||||||
|
statusNormalFn: statusNormalFn,
|
||||||
|
statusFailedFn: statusFailedFn,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
|
||||||
|
monitor.l = l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *HealthCheckMonitor) Start() {
|
||||||
|
go monitor.checkWorker()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *HealthCheckMonitor) Stop() {
|
||||||
|
monitor.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||||
|
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 {
|
||||||
|
if monitor.l != nil {
|
||||||
|
monitor.l.Trace("do one health check success")
|
||||||
|
}
|
||||||
|
if !monitor.statusOK && monitor.statusNormalFn != nil {
|
||||||
|
if monitor.l != nil {
|
||||||
|
monitor.l.Info("health check status change to success")
|
||||||
|
}
|
||||||
|
monitor.statusOK = true
|
||||||
|
monitor.statusNormalFn()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if monitor.l != nil {
|
||||||
|
monitor.l.Warn("do one health check failed: %v", err)
|
||||||
|
}
|
||||||
|
monitor.failedTimes++
|
||||||
|
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
|
||||||
|
if monitor.l != nil {
|
||||||
|
monitor.l.Warn("health check status change to failed")
|
||||||
|
}
|
||||||
|
monitor.statusOK = false
|
||||||
|
monitor.statusFailedFn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(monitor.interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *HealthCheckMonitor) 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 *HealthCheckMonitor) 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 *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
||||||
|
req, err := http.NewRequest("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(ioutil.Discard, resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode/100 != 2 {
|
||||||
|
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
563
client/proxy/proxy.go
Normal file
563
client/proxy/proxy.go
Normal file
@@ -0,0 +1,563 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"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/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
pp "github.com/pires/go-proxyproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
|
type Proxy interface {
|
||||||
|
Run() error
|
||||||
|
|
||||||
|
// InWorkConn accept work connections registered to server.
|
||||||
|
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
||||||
|
|
||||||
|
Close()
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||||
|
baseProxy := BaseProxy{
|
||||||
|
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
pxy = &StcpProxy{
|
||||||
|
BaseProxy: &baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: &baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseProxy struct {
|
||||||
|
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, m *msg.StartWorkConn) {
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, m *msg.StartWorkConn) {
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, m *msg.StartWorkConn) {
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// STCP
|
||||||
|
type StcpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) 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 *StcpProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) 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 *XtcpProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
|
defer conn.Close()
|
||||||
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
|
ProxyName: pxy.cfg.ProxyName,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
}
|
||||||
|
raddr, _ := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||||
|
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("send natHoleClientMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 5 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := clientConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientConn.SetReadDeadline(time.Time{})
|
||||||
|
clientConn.Close()
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
|
// Send detect message
|
||||||
|
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||||
|
if len(array) <= 1 {
|
||||||
|
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
|
}
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
|
/*
|
||||||
|
for i := 1000; i < 65000; i++ {
|
||||||
|
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
|
pxy.Trace("send all detect msg done")
|
||||||
|
|
||||||
|
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||||
|
|
||||||
|
// Listen for clientConn's address and wait for visitor connection
|
||||||
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
var uAddr *net.UDPAddr
|
||||||
|
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Warn("get sid from visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
pxy.Warn("incorrect sid from visitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||||
|
|
||||||
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create yamux server from kcp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Accept()
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("accept for yamux connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
|
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//uConn := ipv4.NewConn(tConn)
|
||||||
|
//uConn.SetTTL(3)
|
||||||
|
|
||||||
|
tConn.Write(content)
|
||||||
|
tConn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, m *msg.StartWorkConn) {
|
||||||
|
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, encKey []byte, m *msg.StartWorkConn) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
remote io.ReadWriteCloser
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
remote = workConn
|
||||||
|
|
||||||
|
if baseInfo.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||||
|
if err != nil {
|
||||||
|
workConn.Close()
|
||||||
|
workConn.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if baseInfo.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we need to send proxy protocol info
|
||||||
|
var extraInfo []byte
|
||||||
|
if baseInfo.ProxyProtocolVersion != "" {
|
||||||
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
|
if m.DstAddr == "" {
|
||||||
|
m.DstAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
h := &pp.Header{
|
||||||
|
Command: pp.PROXY,
|
||||||
|
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||||
|
SourcePort: m.SrcPort,
|
||||||
|
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||||
|
DestinationPort: m.DstPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.SourceAddress.To16() == nil {
|
||||||
|
h.TransportProtocol = pp.TCPv4
|
||||||
|
} else {
|
||||||
|
h.TransportProtocol = pp.TCPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||||
|
h.Version = 1
|
||||||
|
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||||
|
h.Version = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
h.WriteTo(buf)
|
||||||
|
extraInfo = buf.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyPlugin != nil {
|
||||||
|
// if plugin is set, let plugin handle connections first
|
||||||
|
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||||
|
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||||
|
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.Close()
|
||||||
|
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())
|
||||||
|
|
||||||
|
if len(extraInfo) > 0 {
|
||||||
|
localConn.Write(extraInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(localConn, remote)
|
||||||
|
workConn.Debug("join connections closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
139
client/proxy/proxy_manager.go
Normal file
139
client/proxy/proxy_manager.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client/event"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyManager struct {
|
||||||
|
sendCh chan (msg.Message)
|
||||||
|
proxies map[string]*ProxyWrapper
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
logPrefix string
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
|
||||||
|
return &ProxyManager{
|
||||||
|
proxies: make(map[string]*ProxyWrapper),
|
||||||
|
sendCh: msgSendCh,
|
||||||
|
closed: false,
|
||||||
|
logPrefix: logPrefix,
|
||||||
|
Logger: log.NewPrefixLogger(logPrefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) 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 *ProxyManager) Close() {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
for _, pxy := range pm.proxies {
|
||||||
|
pxy.Stop()
|
||||||
|
}
|
||||||
|
pm.proxies = make(map[string]*ProxyWrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.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 *ProxyManager) HandleEvent(evType event.EventType, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
pm.sendCh <- m
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
|
||||||
|
ps := make([]*ProxyStatus, 0)
|
||||||
|
pm.mu.RLock()
|
||||||
|
defer pm.mu.RUnlock()
|
||||||
|
for _, pxy := range pm.proxies {
|
||||||
|
ps = append(ps, pxy.GetStatus())
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
|
delPxyNames := make([]string, 0)
|
||||||
|
for name, pxy := range pm.proxies {
|
||||||
|
del := false
|
||||||
|
cfg, ok := pxyCfgs[name]
|
||||||
|
if !ok {
|
||||||
|
del = true
|
||||||
|
} else {
|
||||||
|
if !pxy.Cfg.Compare(cfg) {
|
||||||
|
del = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if del {
|
||||||
|
delPxyNames = append(delPxyNames, name)
|
||||||
|
delete(pm.proxies, name)
|
||||||
|
|
||||||
|
pxy.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(delPxyNames) > 0 {
|
||||||
|
pm.Info("proxy removed: %v", delPxyNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
addPxyNames := make([]string, 0)
|
||||||
|
for name, cfg := range pxyCfgs {
|
||||||
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
|
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
|
||||||
|
pm.proxies[name] = pxy
|
||||||
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
|
pxy.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addPxyNames) > 0 {
|
||||||
|
pm.Info("proxy added: %v", addPxyNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
244
client/proxy/proxy_wrapper.go
Normal file
244
client/proxy/proxy_wrapper.go
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client/event"
|
||||||
|
"github.com/fatedier/frp/client/health"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyStatusNew = "new"
|
||||||
|
ProxyStatusWaitStart = "wait start"
|
||||||
|
ProxyStatusStartErr = "start error"
|
||||||
|
ProxyStatusRunning = "running"
|
||||||
|
ProxyStatusCheckFailed = "check failed"
|
||||||
|
ProxyStatusClosed = "closed"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statusCheckInterval time.Duration = 3 * time.Second
|
||||||
|
waitResponseTimeout = 20 * time.Second
|
||||||
|
startErrTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyStatus struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Err string `json:"err"`
|
||||||
|
Cfg config.ProxyConf `json:"cfg"`
|
||||||
|
|
||||||
|
// Got from server.
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyWrapper struct {
|
||||||
|
ProxyStatus
|
||||||
|
|
||||||
|
// underlying proxy
|
||||||
|
pxy Proxy
|
||||||
|
|
||||||
|
// if ProxyConf has healcheck config
|
||||||
|
// monitor will watch if it is alive
|
||||||
|
monitor *health.HealthCheckMonitor
|
||||||
|
|
||||||
|
// event handler
|
||||||
|
handler event.EventHandler
|
||||||
|
|
||||||
|
health uint32
|
||||||
|
lastSendStartMsg time.Time
|
||||||
|
lastStartErr time.Time
|
||||||
|
closeCh chan struct{}
|
||||||
|
healthNotifyCh chan struct{}
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
|
||||||
|
baseInfo := cfg.GetBaseInfo()
|
||||||
|
pw := &ProxyWrapper{
|
||||||
|
ProxyStatus: ProxyStatus{
|
||||||
|
Name: baseInfo.ProxyName,
|
||||||
|
Type: baseInfo.ProxyType,
|
||||||
|
Status: ProxyStatusNew,
|
||||||
|
Cfg: cfg,
|
||||||
|
},
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
healthNotifyCh: make(chan struct{}),
|
||||||
|
handler: eventHandler,
|
||||||
|
Logger: log.NewPrefixLogger(logPrefix),
|
||||||
|
}
|
||||||
|
pw.AddLogPrefix(pw.Name)
|
||||||
|
|
||||||
|
if baseInfo.HealthCheckType != "" {
|
||||||
|
pw.health = 1 // means failed
|
||||||
|
pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
||||||
|
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
||||||
|
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
|
||||||
|
pw.monitor.SetLogger(pw.Logger)
|
||||||
|
pw.Trace("enable health check monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.pxy = NewProxy(pw.Cfg)
|
||||||
|
return pw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||||
|
pw.mu.Lock()
|
||||||
|
defer pw.mu.Unlock()
|
||||||
|
if pw.Status != ProxyStatusWaitStart {
|
||||||
|
return fmt.Errorf("status not wait start, ignore start message")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.RemoteAddr = remoteAddr
|
||||||
|
if respErr != "" {
|
||||||
|
pw.Status = ProxyStatusStartErr
|
||||||
|
pw.Err = respErr
|
||||||
|
pw.lastStartErr = time.Now()
|
||||||
|
return fmt.Errorf(pw.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pw.pxy.Run(); err != nil {
|
||||||
|
pw.Status = ProxyStatusStartErr
|
||||||
|
pw.Err = err.Error()
|
||||||
|
pw.lastStartErr = time.Now()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Status = ProxyStatusRunning
|
||||||
|
pw.Err = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) Start() {
|
||||||
|
go pw.checkWorker()
|
||||||
|
if pw.monitor != nil {
|
||||||
|
go pw.monitor.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) 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.Status = ProxyStatusClosed
|
||||||
|
|
||||||
|
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||||
|
CloseProxyMsg: &msg.CloseProxy{
|
||||||
|
ProxyName: pw.Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) checkWorker() {
|
||||||
|
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.Status == ProxyStatusNew ||
|
||||||
|
pw.Status == ProxyStatusCheckFailed ||
|
||||||
|
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
|
||||||
|
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
|
||||||
|
|
||||||
|
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
|
||||||
|
pw.Status = ProxyStatusWaitStart
|
||||||
|
|
||||||
|
var newProxyMsg msg.NewProxy
|
||||||
|
pw.Cfg.MarshalToMsg(&newProxyMsg)
|
||||||
|
pw.lastSendStartMsg = now
|
||||||
|
pw.handler(event.EvStartProxy, &event.StartProxyPayload{
|
||||||
|
NewProxyMsg: &newProxyMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pw.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
pw.mu.Lock()
|
||||||
|
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
|
||||||
|
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
|
||||||
|
CloseProxyMsg: &msg.CloseProxy{
|
||||||
|
ProxyName: pw.Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
|
||||||
|
pw.Status = ProxyStatusCheckFailed
|
||||||
|
}
|
||||||
|
pw.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-pw.closeCh:
|
||||||
|
return
|
||||||
|
case <-time.After(statusCheckInterval):
|
||||||
|
case <-pw.healthNotifyCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) statusNormalCallback() {
|
||||||
|
atomic.StoreUint32(&pw.health, 0)
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
select {
|
||||||
|
case pw.healthNotifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pw.Info("health check success")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) statusFailedCallback() {
|
||||||
|
atomic.StoreUint32(&pw.health, 1)
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
select {
|
||||||
|
case pw.healthNotifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pw.Info("health check failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
|
pw.mu.RLock()
|
||||||
|
pxy := pw.pxy
|
||||||
|
pw.mu.RUnlock()
|
||||||
|
if pxy != nil {
|
||||||
|
workConn.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 *ProxyWrapper) GetStatus() *ProxyStatus {
|
||||||
|
pw.mu.RLock()
|
||||||
|
defer pw.mu.RUnlock()
|
||||||
|
ps := &ProxyStatus{
|
||||||
|
Name: pw.Name,
|
||||||
|
Type: pw.Type,
|
||||||
|
Status: pw.Status,
|
||||||
|
Err: pw.Err,
|
||||||
|
Cfg: pw.Cfg,
|
||||||
|
RemoteAddr: pw.RemoteAddr,
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
}
|
||||||
241
client/service.go
Normal file
241
client/service.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/assets"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
// uniq id got from frps, attach it in loginMsg
|
||||||
|
runId string
|
||||||
|
|
||||||
|
// manager control connection with server
|
||||||
|
ctl *Control
|
||||||
|
ctlMu sync.RWMutex
|
||||||
|
|
||||||
|
pxyCfgs map[string]config.ProxyConf
|
||||||
|
visitorCfgs map[string]config.VisitorConf
|
||||||
|
cfgMu sync.RWMutex
|
||||||
|
|
||||||
|
exit uint32 // 0 means not exit
|
||||||
|
closedCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
|
||||||
|
// Init assets
|
||||||
|
err = assets.Load("")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr = &Service{
|
||||||
|
pxyCfgs: pxyCfgs,
|
||||||
|
visitorCfgs: visitorCfgs,
|
||||||
|
exit: 0,
|
||||||
|
closedCh: make(chan int),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) GetController() *Control {
|
||||||
|
svr.ctlMu.RLock()
|
||||||
|
defer svr.ctlMu.RUnlock()
|
||||||
|
return svr.ctl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Run() error {
|
||||||
|
// first login
|
||||||
|
for {
|
||||||
|
conn, session, err := svr.login()
|
||||||
|
if err != nil {
|
||||||
|
log.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 g.GlbClientCfg.LoginFailExit {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// login success
|
||||||
|
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||||
|
ctl.Run()
|
||||||
|
svr.ctlMu.Lock()
|
||||||
|
svr.ctl = ctl
|
||||||
|
svr.ctlMu.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
|
if g.GlbClientCfg.AdminPort != 0 {
|
||||||
|
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("run admin server error: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-svr.closedCh
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) keepControllerWorking() {
|
||||||
|
maxDelayTime := 20 * time.Second
|
||||||
|
delayTime := time.Second
|
||||||
|
|
||||||
|
for {
|
||||||
|
<-svr.ctl.ClosedDoneCh()
|
||||||
|
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
log.Info("try to reconnect to server...")
|
||||||
|
conn, session, err := svr.login()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("reconnect to server error: %v", err)
|
||||||
|
time.Sleep(delayTime)
|
||||||
|
delayTime = delayTime * 2
|
||||||
|
if delayTime > maxDelayTime {
|
||||||
|
delayTime = maxDelayTime
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// reconnect success, init delayTime
|
||||||
|
delayTime = time.Second
|
||||||
|
|
||||||
|
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
|
||||||
|
ctl.Run()
|
||||||
|
svr.ctlMu.Lock()
|
||||||
|
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 frpNet.Conn, session *fmux.Session, err error) {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if g.GlbClientCfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
if session != nil {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if g.GlbClientCfg.TcpMux {
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
session, err = fmux.Client(conn, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream, errRet := session.OpenStream()
|
||||||
|
if errRet != nil {
|
||||||
|
session.Close()
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = frpNet.WrapConn(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
loginMsg := &msg.Login{
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
Os: runtime.GOOS,
|
||||||
|
PoolCount: g.GlbClientCfg.PoolCount,
|
||||||
|
User: g.GlbClientCfg.User,
|
||||||
|
Version: version.Full(),
|
||||||
|
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
|
||||||
|
Timestamp: now,
|
||||||
|
RunId: svr.runId,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
log.Error("%s", loginRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.runId = loginRespMsg.RunId
|
||||||
|
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||||
|
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
||||||
|
svr.cfgMu.Lock()
|
||||||
|
svr.pxyCfgs = pxyCfgs
|
||||||
|
svr.visitorCfgs = visitorCfgs
|
||||||
|
svr.cfgMu.Unlock()
|
||||||
|
|
||||||
|
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Close() {
|
||||||
|
atomic.StoreUint32(&svr.exit, 1)
|
||||||
|
svr.ctl.Close()
|
||||||
|
close(svr.closedCh)
|
||||||
|
}
|
||||||
325
client/visitor.go
Normal file
325
client/visitor.go
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
Close()
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
ctl: ctl,
|
||||||
|
Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
|
||||||
|
}
|
||||||
|
switch cfg := cfg.(type) {
|
||||||
|
case *config.StcpVisitorConf:
|
||||||
|
visitor = &StcpVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpVisitorConf:
|
||||||
|
visitor = &XtcpVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
ctl *Control
|
||||||
|
l frpNet.Listener
|
||||||
|
closed bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type StcpVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.StcpVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.ctl.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.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 {
|
||||||
|
sv.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.XtcpVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("xtcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new xtcp user connection")
|
||||||
|
if g.GlbClientCfg.ServerUdpPort == 0 {
|
||||||
|
sv.Error("xtcp is not supported by server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("resolve server UDP addr error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("dial server udp addr error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 10 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := visitorConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
|
// Close visitorConn, so we can use it's local address.
|
||||||
|
visitorConn.Close()
|
||||||
|
|
||||||
|
// send sid message to client
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("resolve client udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("dial client udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
|
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||||
|
|
||||||
|
// read ack sid from client
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
n, err = lConn.Read(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get sid from client error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
sv.Warn("incorrect sid from client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
|
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
// wrap kcp connection
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Client(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create yamux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Open()
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("open yamux stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, muxConn)
|
||||||
|
sv.Debug("join connections closed")
|
||||||
|
}
|
||||||
123
client/visitor_manager.go
Normal file
123
client/visitor_manager.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 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 client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorManager struct {
|
||||||
|
ctl *Control
|
||||||
|
|
||||||
|
cfgs map[string]config.VisitorConf
|
||||||
|
visitors map[string]Visitor
|
||||||
|
|
||||||
|
checkInterval time.Duration
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorManager(ctl *Control) *VisitorManager {
|
||||||
|
return &VisitorManager{
|
||||||
|
ctl: ctl,
|
||||||
|
cfgs: make(map[string]config.VisitorConf),
|
||||||
|
visitors: make(map[string]Visitor),
|
||||||
|
checkInterval: 10 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) Run() {
|
||||||
|
for {
|
||||||
|
time.Sleep(vm.checkInterval)
|
||||||
|
vm.mu.Lock()
|
||||||
|
for _, cfg := range vm.cfgs {
|
||||||
|
name := cfg.GetBaseInfo().ProxyName
|
||||||
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
|
log.Info("try to start visitor [%s]", name)
|
||||||
|
vm.startVisitor(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vm.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold lock before calling this function.
|
||||||
|
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||||
|
name := cfg.GetBaseInfo().ProxyName
|
||||||
|
visitor := NewVisitor(vm.ctl, cfg)
|
||||||
|
err = visitor.Run()
|
||||||
|
if err != nil {
|
||||||
|
visitor.Warn("start error: %v", err)
|
||||||
|
} else {
|
||||||
|
vm.visitors[name] = visitor
|
||||||
|
visitor.Info("start visitor success")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
delNames := make([]string, 0)
|
||||||
|
for name, oldCfg := range vm.cfgs {
|
||||||
|
del := false
|
||||||
|
cfg, ok := cfgs[name]
|
||||||
|
if !ok {
|
||||||
|
del = true
|
||||||
|
} else {
|
||||||
|
if !oldCfg.Compare(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 {
|
||||||
|
log.Info("visitor removed: %v", delNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
addNames := make([]string, 0)
|
||||||
|
for name, cfg := range cfgs {
|
||||||
|
if _, ok := vm.cfgs[name]; !ok {
|
||||||
|
vm.cfgs[name] = cfg
|
||||||
|
addNames = append(addNames, name)
|
||||||
|
vm.startVisitor(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addNames) > 0 {
|
||||||
|
log.Info("visitor added: %v", addNames)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) Close() {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
for _, v := range vm.visitors {
|
||||||
|
v.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,18 +12,17 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package conn
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||||
"net"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListenUDP(bindAddr string, bindPort int64) (conn *net.UDPConn, err error) {
|
func main() {
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
crypto.DefaultSalt = "frp"
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
sub.Execute()
|
||||||
}
|
|
||||||
conn, err = net.ListenUDP("udp", udpAddr)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
96
cmd/frpc/sub/http.go
Normal file
96
cmd/frpc/sub/http.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
|
||||||
|
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
||||||
|
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(httpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpCmd = &cobra.Command{
|
||||||
|
Use: "http",
|
||||||
|
Short: "Run frpc with a single http proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.HttpProxyConf{}
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.HttpProxy
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||||
|
cfg.SubDomain = subDomain
|
||||||
|
cfg.Locations = strings.Split(locations, ",")
|
||||||
|
cfg.HttpUser = httpUser
|
||||||
|
cfg.HttpPwd = httpPwd
|
||||||
|
cfg.HostHeaderRewrite = hostHeaderRewrite
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
|
cfg.ProxyName: cfg,
|
||||||
|
}
|
||||||
|
err = startService(proxyConfs, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
88
cmd/frpc/sub/https.go
Normal file
88
cmd/frpc/sub/https.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
||||||
|
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
||||||
|
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(httpsCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpsCmd = &cobra.Command{
|
||||||
|
Use: "https",
|
||||||
|
Short: "Run frpc with a single https proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.HttpsProxyConf{}
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.HttpsProxy
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
cfg.CustomDomains = strings.Split(customDomains, ",")
|
||||||
|
cfg.SubDomain = subDomain
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
|
cfg.ProxyName: cfg,
|
||||||
|
}
|
||||||
|
err = startService(proxyConfs, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
91
cmd/frpc/sub/reload.go
Normal file
91
cmd/frpc/sub/reload.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(reloadCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reloadCmd = &cobra.Command{
|
||||||
|
Use: "reload",
|
||||||
|
Short: "Hot-Reload frpc configuration",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = reload()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc reload error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("reload success\n")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func reload() error {
|
||||||
|
if g.GlbClientCfg.AdminPort == 0 {
|
||||||
|
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
|
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||||
|
g.GlbClientCfg.AdminPwd))
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", authStr)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
|
}
|
||||||
224
cmd/frpc/sub/root.go
Normal file
224
cmd/frpc/sub/root.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// 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"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CfgFileTypeIni = iota
|
||||||
|
CfgFileTypeCmd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgFile string
|
||||||
|
showVersion bool
|
||||||
|
|
||||||
|
serverAddr string
|
||||||
|
user string
|
||||||
|
protocol string
|
||||||
|
token string
|
||||||
|
logLevel string
|
||||||
|
logFile string
|
||||||
|
logMaxDays int
|
||||||
|
|
||||||
|
proxyName string
|
||||||
|
localIp string
|
||||||
|
localPort int
|
||||||
|
remotePort int
|
||||||
|
useEncryption bool
|
||||||
|
useCompression bool
|
||||||
|
customDomains string
|
||||||
|
subDomain string
|
||||||
|
httpUser string
|
||||||
|
httpPwd string
|
||||||
|
locations string
|
||||||
|
hostHeaderRewrite string
|
||||||
|
role string
|
||||||
|
sk string
|
||||||
|
serverName string
|
||||||
|
bindAddr string
|
||||||
|
bindPort int
|
||||||
|
|
||||||
|
kcpDoneCh chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
|
kcpDoneCh = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not show command usage here.
|
||||||
|
err := runClient(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSignal(svr *client.Service) {
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-ch
|
||||||
|
svr.Close()
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
close(kcpDoneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClientCommonCfg(fileType int, content string) (err error) {
|
||||||
|
if fileType == CfgFileTypeIni {
|
||||||
|
err = parseClientCommonCfgFromIni(content)
|
||||||
|
} else if fileType == CfgFileTypeCmd {
|
||||||
|
err = parseClientCommonCfgFromCmd()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.GlbClientCfg.ClientCommonConf.Check()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClientCommonCfgFromIni(content string) (err error) {
|
||||||
|
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.GlbClientCfg.ClientCommonConf = *cfg
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClientCommonCfgFromCmd() (err error) {
|
||||||
|
strs := strings.Split(serverAddr, ":")
|
||||||
|
if len(strs) < 2 {
|
||||||
|
err = fmt.Errorf("invalid server_addr")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strs[0] != "" {
|
||||||
|
g.GlbClientCfg.ServerAddr = strs[0]
|
||||||
|
}
|
||||||
|
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid server_addr")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.GlbClientCfg.User = user
|
||||||
|
g.GlbClientCfg.Protocol = protocol
|
||||||
|
g.GlbClientCfg.Token = token
|
||||||
|
g.GlbClientCfg.LogLevel = logLevel
|
||||||
|
g.GlbClientCfg.LogFile = logFile
|
||||||
|
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
|
||||||
|
if logFile == "console" {
|
||||||
|
g.GlbClientCfg.LogWay = "console"
|
||||||
|
} else {
|
||||||
|
g.GlbClientCfg.LogWay = "file"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runClient(cfgFilePath string) (err error) {
|
||||||
|
var content string
|
||||||
|
content, err = config.GetRenderedConfFromFile(cfgFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.GlbClientCfg.CfgFile = cfgFilePath
|
||||||
|
|
||||||
|
err = parseClientCommonCfg(CfgFileTypeIni, content)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = startService(pxyCfgs, visitorCfgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
|
||||||
|
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
|
||||||
|
if g.GlbClientCfg.DnsServer != "" {
|
||||||
|
s := g.GlbClientCfg.DnsServer
|
||||||
|
if !strings.Contains(s, ":") {
|
||||||
|
s += ":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", s)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the exit signal if we use kcp.
|
||||||
|
if g.GlbClientCfg.Protocol == "kcp" {
|
||||||
|
go handleSignal(svr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svr.Run()
|
||||||
|
if g.GlbClientCfg.Protocol == "kcp" {
|
||||||
|
<-kcpDoneCh
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
155
cmd/frpc/sub/status.go
Normal file
155
cmd/frpc/sub/status.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 (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rodaine/table"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/client"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(statusCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Short: "Overview of all proxies status",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = status()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc get status error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func status() error {
|
||||||
|
if g.GlbClientCfg.AdminPort == 0 {
|
||||||
|
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
|
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
|
||||||
|
g.GlbClientCfg.AdminPwd))
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", authStr)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res := &client.StatusResp{}
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Proxy Status...")
|
||||||
|
if len(res.Tcp) > 0 {
|
||||||
|
fmt.Printf("TCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Tcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Udp) > 0 {
|
||||||
|
fmt.Printf("UDP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Udp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Http) > 0 {
|
||||||
|
fmt.Printf("HTTP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Http {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Https) > 0 {
|
||||||
|
fmt.Printf("HTTPS")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Https {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Stcp) > 0 {
|
||||||
|
fmt.Printf("STCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Stcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Xtcp) > 0 {
|
||||||
|
fmt.Printf("XTCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Xtcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
113
cmd/frpc/sub/stcp.go
Normal file
113
cmd/frpc/sub/stcp.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||||
|
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||||
|
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(stcpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stcpCmd = &cobra.Command{
|
||||||
|
Use: "stcp",
|
||||||
|
Short: "Run frpc with a single stcp proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := make(map[string]config.ProxyConf)
|
||||||
|
visitorConfs := make(map[string]config.VisitorConf)
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "server" {
|
||||||
|
cfg := &config.StcpProxyConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.StcpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
proxyConfs[cfg.ProxyName] = cfg
|
||||||
|
} else if role == "visitor" {
|
||||||
|
cfg := &config.StcpVisitorConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.StcpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.ServerName = serverName
|
||||||
|
cfg.BindAddr = bindAddr
|
||||||
|
cfg.BindPort = bindPort
|
||||||
|
err = cfg.Check()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
visitorConfs[cfg.ProxyName] = cfg
|
||||||
|
} else {
|
||||||
|
fmt.Println("invalid role")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = startService(proxyConfs, visitorConfs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
85
cmd/frpc/sub/tcp.go
Normal file
85
cmd/frpc/sub/tcp.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||||
|
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(tcpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcpCmd = &cobra.Command{
|
||||||
|
Use: "tcp",
|
||||||
|
Short: "Run frpc with a single tcp proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.TcpProxyConf{}
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.TcpProxy
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
cfg.RemotePort = remotePort
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
|
cfg.ProxyName: cfg,
|
||||||
|
}
|
||||||
|
err = startService(proxyConfs, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
85
cmd/frpc/sub/udp.go
Normal file
85
cmd/frpc/sub/udp.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
||||||
|
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(udpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var udpCmd = &cobra.Command{
|
||||||
|
Use: "udp",
|
||||||
|
Short: "Run frpc with a single udp proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.UdpProxyConf{}
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.UdpProxy
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
cfg.RemotePort = remotePort
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := map[string]config.ProxyConf{
|
||||||
|
cfg.ProxyName: cfg,
|
||||||
|
}
|
||||||
|
err = startService(proxyConfs, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
113
cmd/frpc/sub/xtcp.go
Normal file
113
cmd/frpc/sub/xtcp.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||||
|
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||||
|
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(xtcpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xtcpCmd = &cobra.Command{
|
||||||
|
Use: "xtcp",
|
||||||
|
Short: "Run frpc with a single xtcp proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := make(map[string]config.ProxyConf)
|
||||||
|
visitorConfs := make(map[string]config.VisitorConf)
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "server" {
|
||||||
|
cfg := &config.XtcpProxyConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
proxyConfs[cfg.ProxyName] = cfg
|
||||||
|
} else if role == "visitor" {
|
||||||
|
cfg := &config.XtcpVisitorConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.ServerName = serverName
|
||||||
|
cfg.BindAddr = bindAddr
|
||||||
|
cfg.BindPort = bindPort
|
||||||
|
err = cfg.Check()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
visitorConfs[cfg.ProxyName] = cfg
|
||||||
|
} else {
|
||||||
|
fmt.Println("invalid role")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = startService(proxyConfs, visitorConfs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
27
cmd/frps/main.go
Normal file
27
cmd/frps/main.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 (
|
||||||
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
|
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
crypto.DefaultSalt = "frp"
|
||||||
|
|
||||||
|
Execute()
|
||||||
|
}
|
||||||
208
cmd/frps/root.go
Normal file
208
cmd/frps/root.go
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/server"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CfgFileTypeIni = iota
|
||||||
|
CfgFileTypeCmd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgFile string
|
||||||
|
showVersion bool
|
||||||
|
|
||||||
|
bindAddr string
|
||||||
|
bindPort int
|
||||||
|
bindUdpPort int
|
||||||
|
kcpBindPort int
|
||||||
|
proxyBindAddr string
|
||||||
|
vhostHttpPort int
|
||||||
|
vhostHttpsPort int
|
||||||
|
vhostHttpTimeout int64
|
||||||
|
dashboardAddr string
|
||||||
|
dashboardPort int
|
||||||
|
dashboardUser string
|
||||||
|
dashboardPwd string
|
||||||
|
assetsDir string
|
||||||
|
logFile string
|
||||||
|
logLevel string
|
||||||
|
logMaxDays int64
|
||||||
|
token string
|
||||||
|
subDomainHost string
|
||||||
|
tcpMux bool
|
||||||
|
allowPorts string
|
||||||
|
maxPoolCount int64
|
||||||
|
maxPortsPerClient int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
|
||||||
|
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
|
||||||
|
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||||
|
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 err error
|
||||||
|
if cfgFile != "" {
|
||||||
|
var content string
|
||||||
|
content, err = config.GetRenderedConfFromFile(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.GlbServerCfg.CfgFile = cfgFile
|
||||||
|
err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||||
|
} else {
|
||||||
|
err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runServer()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseServerCommonCfg(fileType int, content string) (err error) {
|
||||||
|
if fileType == CfgFileTypeIni {
|
||||||
|
err = parseServerCommonCfgFromIni(content)
|
||||||
|
} else if fileType == CfgFileTypeCmd {
|
||||||
|
err = parseServerCommonCfgFromCmd()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.GlbServerCfg.ServerCommonConf.Check()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseServerCommonCfgFromIni(content string) (err error) {
|
||||||
|
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.GlbServerCfg.ServerCommonConf = *cfg
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseServerCommonCfgFromCmd() (err error) {
|
||||||
|
g.GlbServerCfg.BindAddr = bindAddr
|
||||||
|
g.GlbServerCfg.BindPort = bindPort
|
||||||
|
g.GlbServerCfg.BindUdpPort = bindUdpPort
|
||||||
|
g.GlbServerCfg.KcpBindPort = kcpBindPort
|
||||||
|
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
|
||||||
|
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
|
||||||
|
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
|
||||||
|
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
|
||||||
|
g.GlbServerCfg.DashboardAddr = dashboardAddr
|
||||||
|
g.GlbServerCfg.DashboardPort = dashboardPort
|
||||||
|
g.GlbServerCfg.DashboardUser = dashboardUser
|
||||||
|
g.GlbServerCfg.DashboardPwd = dashboardPwd
|
||||||
|
g.GlbServerCfg.LogFile = logFile
|
||||||
|
g.GlbServerCfg.LogLevel = logLevel
|
||||||
|
g.GlbServerCfg.LogMaxDays = logMaxDays
|
||||||
|
g.GlbServerCfg.Token = token
|
||||||
|
g.GlbServerCfg.SubDomainHost = subDomainHost
|
||||||
|
if len(allowPorts) > 0 {
|
||||||
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
|
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, port := range ports {
|
||||||
|
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||||
|
|
||||||
|
if logFile == "console" {
|
||||||
|
g.GlbServerCfg.LogWay = "console"
|
||||||
|
} else {
|
||||||
|
g.GlbServerCfg.LogWay = "file"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer() (err error) {
|
||||||
|
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
|
||||||
|
g.GlbServerCfg.LogMaxDays)
|
||||||
|
svr, err := server.NewService()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Start frps success")
|
||||||
|
server.ServerService = svr
|
||||||
|
svr.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,81 +1,9 @@
|
|||||||
# [common] is integral section
|
|
||||||
[common]
|
[common]
|
||||||
# A literal address or host name for IPv6 must be enclosed
|
server_addr = 127.0.0.1
|
||||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
|
||||||
server_addr = 0.0.0.0
|
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
|
||||||
# http_proxy = http://user:pwd@192.168.1.128:8080
|
|
||||||
|
|
||||||
# console or real logFile path like ./frpc.log
|
|
||||||
log_file = ./frpc.log
|
|
||||||
|
|
||||||
# debug, info, warn, error
|
|
||||||
log_level = info
|
|
||||||
|
|
||||||
log_max_days = 3
|
|
||||||
|
|
||||||
# for authentication
|
|
||||||
auth_token = 123
|
|
||||||
|
|
||||||
# for privilege mode
|
|
||||||
privilege_token = 12345678
|
|
||||||
|
|
||||||
|
|
||||||
# ssh is the proxy name same as server's configuration
|
|
||||||
[ssh]
|
[ssh]
|
||||||
# tcp | http, default is tcp
|
|
||||||
type = tcp
|
type = tcp
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 22
|
local_port = 22
|
||||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
remote_port = 6000
|
||||||
use_encryption = true
|
|
||||||
# default is false
|
|
||||||
use_gzip = false
|
|
||||||
# connections will be established in advance, default value is zero
|
|
||||||
pool_count = 10
|
|
||||||
|
|
||||||
[dns]
|
|
||||||
type = udp
|
|
||||||
local_ip = 114.114.114.114
|
|
||||||
local_port = 53
|
|
||||||
|
|
||||||
# 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, the domains are set in frps.ini
|
|
||||||
[web01]
|
|
||||||
type = http
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 80
|
|
||||||
use_gzip = true
|
|
||||||
pool_count = 20
|
|
||||||
# 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 = test
|
|
||||||
|
|
||||||
[web02]
|
|
||||||
type = http
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 8000
|
|
||||||
|
|
||||||
[privilege_ssh]
|
|
||||||
# if privilege_mode is enabled, this proxy will be created automatically
|
|
||||||
privilege_mode = true
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
use_encryption = true
|
|
||||||
use_gzip = false
|
|
||||||
remote_port = 6001
|
|
||||||
|
|
||||||
[privilege_web]
|
|
||||||
privilege_mode = true
|
|
||||||
type = http
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 80
|
|
||||||
use_gzip = true
|
|
||||||
custom_domains = web03.yourdomain.com
|
|
||||||
host_header_rewrite = example.com
|
|
||||||
subdomain = dev
|
|
||||||
|
|||||||
243
conf/frpc_full.ini
Normal file
243
conf/frpc_full.ini
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# [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 or socks5 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
token = 12345678
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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 and websocket, default is tcp
|
||||||
|
protocol = tcp
|
||||||
|
|
||||||
|
# if tls_enable is true, frpc will connect frps by tls
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
# 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 seperated 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 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
|
||||||
|
# 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
|
||||||
|
|
||||||
|
[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://test.frps.com
|
||||||
|
subdomain = web01
|
||||||
|
custom_domains = web02.yourdomain.com
|
||||||
|
# locations is only available for http type
|
||||||
|
locations = /,/pic
|
||||||
|
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 = web01
|
||||||
|
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
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
# 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 = 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
|
||||||
|
|
||||||
|
[p2p_tcp_visitor]
|
||||||
|
role = visitor
|
||||||
|
type = xtcp
|
||||||
|
server_name = p2p_tcp
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 9001
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[common]
|
|
||||||
server_addr = 0.0.0.0
|
|
||||||
server_port = 7000
|
|
||||||
auth_token = 123
|
|
||||||
privilege_token = 12345678
|
|
||||||
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
@@ -1,71 +1,2 @@
|
|||||||
# [common] is integral section
|
|
||||||
[common]
|
[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
|
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
|
|
||||||
|
|
||||||
# debug, info, warn, error
|
|
||||||
log_level = info
|
|
||||||
|
|
||||||
log_max_days = 3
|
|
||||||
|
|
||||||
# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_token is correct
|
|
||||||
privilege_mode = true
|
|
||||||
privilege_token = 12345678
|
|
||||||
|
|
||||||
# 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 = 100
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# ssh is the proxy name, client will use this name and auth_token to connect to server
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
auth_token = 123
|
|
||||||
bind_addr = 0.0.0.0
|
|
||||||
listen_port = 6000
|
|
||||||
|
|
||||||
[dns]
|
|
||||||
type = udp
|
|
||||||
auth_token = 123
|
|
||||||
bind_addr = 0.0.0.0
|
|
||||||
listen_port = 5353
|
|
||||||
|
|
||||||
[web01]
|
|
||||||
# if type equals http, vhost_http_port must be set
|
|
||||||
type = http
|
|
||||||
auth_token = 123
|
|
||||||
# if proxy type equals http, custom_domains must be set separated by commas
|
|
||||||
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
|
|
||||||
|
|
||||||
[web02]
|
|
||||||
# if type equals https, vhost_https_port must be set
|
|
||||||
type = https
|
|
||||||
auth_token = 123
|
|
||||||
custom_domains = web02.yourdomain.com
|
|
||||||
|
|||||||
70
conf/frps_full.ini
Normal file
70
conf/frps_full.ini
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# [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 to help make udp hole to penetrate nat
|
||||||
|
bind_udp_port = 7001
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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, 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
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# custom 404 page for HTTP requests
|
||||||
|
# custom_404_page = /path/to/404.html
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[common]
|
|
||||||
bind_addr = 0.0.0.0
|
|
||||||
bind_port = 7000
|
|
||||||
vhost_http_port = 80
|
|
||||||
vhost_https_port = 443
|
|
||||||
dashboard_port = 7500
|
|
||||||
privilege_mode = true
|
|
||||||
privilege_token = 12345678
|
|
||||||
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
auth_token = 123
|
|
||||||
bind_addr = 0.0.0.0
|
|
||||||
listen_port = 6000
|
|
||||||
14
conf/systemd/frpc.service
Normal file
14
conf/systemd/frpc.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
conf/systemd/frpc@.service
Normal file
14
conf/systemd/frpc@.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps.service
Normal file
13
conf/systemd/frps.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps@.service
Normal file
13
conf/systemd/frps@.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 31 KiB |
BIN
doc/pic/donate-wechatpay.png
Normal file
BIN
doc/pic/donate-wechatpay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
doc/pic/zsxq.jpg
Normal file
BIN
doc/pic/zsxq.jpg
Normal file
Binary file not shown.
|
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
|
|
||||||
```
|
|
||||||
32
g/g.go
Normal file
32
g/g.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package g
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
GlbClientCfg *ClientCfg
|
||||||
|
GlbServerCfg *ServerCfg
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
GlbClientCfg = &ClientCfg{
|
||||||
|
ClientCommonConf: *config.GetDefaultClientConf(),
|
||||||
|
}
|
||||||
|
GlbServerCfg = &ServerCfg{
|
||||||
|
ServerCommonConf: *config.GetDefaultServerConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCfg struct {
|
||||||
|
config.ClientCommonConf
|
||||||
|
|
||||||
|
CfgFile string
|
||||||
|
ServerUdpPort int // this is configured by login response from frps
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerCfg struct {
|
||||||
|
config.ServerCommonConf
|
||||||
|
|
||||||
|
CfgFile string
|
||||||
|
}
|
||||||
32
go.mod
Normal file
32
go.mod
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||||
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||||
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||||
|
github.com/gorilla/mux v1.7.3
|
||||||
|
github.com/gorilla/websocket v1.4.0
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||||
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
|
github.com/rakyll/statik v0.1.1
|
||||||
|
github.com/rodaine/table v1.0.0
|
||||||
|
github.com/spf13/cobra v0.0.3
|
||||||
|
github.com/spf13/pflag v1.0.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
)
|
||||||
61
go.sum
Normal file
61
go.sum
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||||
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||||
|
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/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||||
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||||
|
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||||
|
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||||
|
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||||
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||||
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
236
models/config/client_common.go
Normal file
236
models/config/client_common.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// client common config
|
||||||
|
type ClientCommonConf struct {
|
||||||
|
ServerAddr string `json:"server_addr"`
|
||||||
|
ServerPort int `json:"server_port"`
|
||||||
|
HttpProxy string `json:"http_proxy"`
|
||||||
|
LogFile string `json:"log_file"`
|
||||||
|
LogWay string `json:"log_way"`
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
AdminAddr string `json:"admin_addr"`
|
||||||
|
AdminPort int `json:"admin_port"`
|
||||||
|
AdminUser string `json:"admin_user"`
|
||||||
|
AdminPwd string `json:"admin_pwd"`
|
||||||
|
PoolCount int `json:"pool_count"`
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
User string `json:"user"`
|
||||||
|
DnsServer string `json:"dns_server"`
|
||||||
|
LoginFailExit bool `json:"login_fail_exit"`
|
||||||
|
Start map[string]struct{} `json:"start"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
TLSEnable bool `json:"tls_enable"`
|
||||||
|
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||||
|
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultClientConf() *ClientCommonConf {
|
||||||
|
return &ClientCommonConf{
|
||||||
|
ServerAddr: "0.0.0.0",
|
||||||
|
ServerPort: 7000,
|
||||||
|
HttpProxy: os.Getenv("http_proxy"),
|
||||||
|
LogFile: "console",
|
||||||
|
LogWay: "console",
|
||||||
|
LogLevel: "info",
|
||||||
|
LogMaxDays: 3,
|
||||||
|
Token: "",
|
||||||
|
AdminAddr: "127.0.0.1",
|
||||||
|
AdminPort: 0,
|
||||||
|
AdminUser: "",
|
||||||
|
AdminPwd: "",
|
||||||
|
PoolCount: 1,
|
||||||
|
TcpMux: true,
|
||||||
|
User: "",
|
||||||
|
DnsServer: "",
|
||||||
|
LoginFailExit: true,
|
||||||
|
Start: make(map[string]struct{}),
|
||||||
|
Protocol: "tcp",
|
||||||
|
TLSEnable: false,
|
||||||
|
HeartBeatInterval: 30,
|
||||||
|
HeartBeatTimeout: 90,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
|
||||||
|
cfg = defaultCfg
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = GetDefaultClientConf()
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
v int64
|
||||||
|
)
|
||||||
|
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
|
||||||
|
cfg.ServerAddr = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "server_port"); ok {
|
||||||
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid server_port")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.ServerPort = int(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
|
||||||
|
cfg.HttpProxy = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||||
|
cfg.LogFile = tmpStr
|
||||||
|
if cfg.LogFile == "console" {
|
||||||
|
cfg.LogWay = "console"
|
||||||
|
} else {
|
||||||
|
cfg.LogWay = "file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||||
|
cfg.LogLevel = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
|
cfg.LogMaxDays = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "token"); ok {
|
||||||
|
cfg.Token = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
|
||||||
|
cfg.AdminAddr = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
|
cfg.AdminPort = int(v)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid admin_port")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
|
||||||
|
cfg.AdminUser = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
|
||||||
|
cfg.AdminPwd = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
|
cfg.PoolCount = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||||
|
cfg.TcpMux = false
|
||||||
|
} else {
|
||||||
|
cfg.TcpMux = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "user"); ok {
|
||||||
|
cfg.User = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
|
||||||
|
cfg.DnsServer = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "start"); ok {
|
||||||
|
proxyNames := strings.Split(tmpStr, ",")
|
||||||
|
for _, name := range proxyNames {
|
||||||
|
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
|
||||||
|
cfg.LoginFailExit = false
|
||||||
|
} else {
|
||||||
|
cfg.LoginFailExit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "protocol"); ok {
|
||||||
|
// Now it only support tcp and kcp and websocket.
|
||||||
|
if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid protocol")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.Protocol = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||||
|
cfg.TLSEnable = true
|
||||||
|
} else {
|
||||||
|
cfg.TLSEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.HeartBeatTimeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.HeartBeatInterval = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ClientCommonConf) Check() (err error) {
|
||||||
|
if cfg.HeartBeatInterval <= 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
974
models/config/proxy.go
Normal file
974
models/config/proxy.go
Normal file
@@ -0,0 +1,974 @@
|
|||||||
|
// 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{})
|
||||||
|
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
GetBaseInfo() *BaseProxyConf
|
||||||
|
UnmarshalFromMsg(pMsg *msg.NewProxy)
|
||||||
|
UnmarshalFromIni(prefix string, name string, conf ini.Section) error
|
||||||
|
MarshalToMsg(pMsg *msg.NewProxy)
|
||||||
|
CheckForCli() error
|
||||||
|
CheckForSvr() error
|
||||||
|
Compare(conf ProxyConf) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyConfFromMsg(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.UnmarshalFromMsg(pMsg)
|
||||||
|
err = cfg.CheckForSvr()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyConfFromIni(prefix string, 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
|
||||||
|
}
|
||||||
|
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.CheckForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
GroupKey string `json:"group_key"`
|
||||||
|
|
||||||
|
// only used for client
|
||||||
|
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||||
|
LocalSvrConf
|
||||||
|
HealthCheckConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||||
|
if cfg.ProxyName != cmp.ProxyName ||
|
||||||
|
cfg.ProxyType != cmp.ProxyType ||
|
||||||
|
cfg.UseEncryption != cmp.UseEncryption ||
|
||||||
|
cfg.UseCompression != cmp.UseCompression ||
|
||||||
|
cfg.Group != cmp.Group ||
|
||||||
|
cfg.GroupKey != cmp.GroupKey ||
|
||||||
|
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.ProxyName = pMsg.ProxyName
|
||||||
|
cfg.ProxyType = pMsg.ProxyType
|
||||||
|
cfg.UseEncryption = pMsg.UseEncryption
|
||||||
|
cfg.UseCompression = pMsg.UseCompression
|
||||||
|
cfg.Group = pMsg.Group
|
||||||
|
cfg.GroupKey = pMsg.GroupKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
cfg.ProxyName = prefix + 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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Group = section["group"]
|
||||||
|
cfg.GroupKey = section["group_key"]
|
||||||
|
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||||
|
|
||||||
|
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
|
||||||
|
cfg.HealthCheckAddr = cfg.LocalIp + fmt.Sprintf(":%d", cfg.LocalPort)
|
||||||
|
}
|
||||||
|
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckUrl != "" {
|
||||||
|
s := fmt.Sprintf("http://%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
if !strings.HasPrefix(cfg.HealthCheckUrl, "/") {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
pMsg.ProxyName = cfg.ProxyName
|
||||||
|
pMsg.ProxyType = cfg.ProxyType
|
||||||
|
pMsg.UseEncryption = cfg.UseEncryption
|
||||||
|
pMsg.UseCompression = cfg.UseCompression
|
||||||
|
pMsg.Group = cfg.Group
|
||||||
|
pMsg.GroupKey = cfg.GroupKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||||
|
if cfg.ProxyProtocolVersion != "" {
|
||||||
|
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||||
|
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.HealthCheckConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind info
|
||||||
|
type BindInfoConf struct {
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
|
||||||
|
if cfg.RemotePort != cmp.RemotePort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BindInfoConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.RemotePort = pMsg.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
v int64
|
||||||
|
)
|
||||||
|
if tmpStr, ok = section["remote_port"]; ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
|
||||||
|
} else {
|
||||||
|
cfg.RemotePort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BindInfoConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
pMsg.RemotePort = cfg.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain info
|
||||||
|
type DomainConf struct {
|
||||||
|
CustomDomains []string `json:"custom_domains"`
|
||||||
|
SubDomain string `json:"sub_domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) compare(cmp *DomainConf) bool {
|
||||||
|
if strings.Join(cfg.CustomDomains, " ") != strings.Join(cmp.CustomDomains, " ") ||
|
||||||
|
cfg.SubDomain != cmp.SubDomain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.CustomDomains = pMsg.CustomDomains
|
||||||
|
cfg.SubDomain = pMsg.SubDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) UnmarshalFromIni(prefix string, 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
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
pMsg.CustomDomains = cfg.CustomDomains
|
||||||
|
pMsg.SubDomain = cfg.SubDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) check() (err error) {
|
||||||
|
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
|
||||||
|
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) checkForCli() (err error) {
|
||||||
|
if err = cfg.check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) checkForSvr() (err error) {
|
||||||
|
if err = cfg.check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range cfg.CustomDomains {
|
||||||
|
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||||
|
if strings.Contains(domain, subDomainHost) {
|
||||||
|
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.SubDomain != "" {
|
||||||
|
if subDomainHost == "" {
|
||||||
|
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
||||||
|
}
|
||||||
|
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||||
|
return fmt.Errorf("'.' and '*' is not supported in subdomain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local service info
|
||||||
|
type LocalSvrConf struct {
|
||||||
|
LocalIp string `json:"local_ip"`
|
||||||
|
LocalPort int `json:"local_port"`
|
||||||
|
|
||||||
|
Plugin string `json:"plugin"`
|
||||||
|
PluginParams map[string]string `json:"plugin_params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
|
||||||
|
if cfg.LocalIp != cmp.LocalIp ||
|
||||||
|
cfg.LocalPort != cmp.LocalPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if cfg.Plugin != cmp.Plugin ||
|
||||||
|
len(cfg.PluginParams) != len(cmp.PluginParams) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k, v := range cfg.PluginParams {
|
||||||
|
value, ok := cmp.PluginParams[k]
|
||||||
|
if !ok || v != value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *LocalSvrConf) checkForCli() (err error) {
|
||||||
|
if cfg.Plugin == "" {
|
||||||
|
if cfg.LocalIp == "" {
|
||||||
|
err = fmt.Errorf("local ip or plugin is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.LocalPort <= 0 {
|
||||||
|
err = fmt.Errorf("error local_port")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check info
|
||||||
|
type HealthCheckConf struct {
|
||||||
|
HealthCheckType string `json:"health_check_type"` // tcp | http
|
||||||
|
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
|
||||||
|
HealthCheckMaxFailed int `json:"health_check_max_failed"`
|
||||||
|
HealthCheckIntervalS int `json:"health_check_interval_s"`
|
||||||
|
HealthCheckUrl string `json:"health_check_url"`
|
||||||
|
|
||||||
|
// local_ip + local_port
|
||||||
|
HealthCheckAddr string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
|
||||||
|
if cfg.HealthCheckType != cmp.HealthCheckType ||
|
||||||
|
cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS ||
|
||||||
|
cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed ||
|
||||||
|
cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS ||
|
||||||
|
cfg.HealthCheckUrl != cmp.HealthCheckUrl {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
cfg.HealthCheckType = section["health_check_type"]
|
||||||
|
cfg.HealthCheckUrl = section["health_check_url"]
|
||||||
|
|
||||||
|
if tmpStr, ok := section["health_check_timeout_s"]; ok {
|
||||||
|
if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] health_check_timeout_s error", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok := section["health_check_max_failed"]; ok {
|
||||||
|
if cfg.HealthCheckMaxFailed, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] health_check_max_failed error", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok := section["health_check_interval_s"]; ok {
|
||||||
|
if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HealthCheckConf) checkForCli() error {
|
||||||
|
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
||||||
|
return fmt.Errorf("unsupport health check type")
|
||||||
|
}
|
||||||
|
if cfg.HealthCheckType != "" {
|
||||||
|
if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" {
|
||||||
|
return fmt.Errorf("health_check_url is required for health check type 'http'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP
|
||||||
|
type TcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
BindInfoConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*TcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
|
||||||
|
|
||||||
|
// UDP
|
||||||
|
type UdpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
BindInfoConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*UdpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
|
||||||
|
|
||||||
|
// HTTP
|
||||||
|
type HttpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
DomainConf
|
||||||
|
|
||||||
|
Locations []string `json:"locations"`
|
||||||
|
HttpUser string `json:"http_user"`
|
||||||
|
HttpPwd string `json:"http_pwd"`
|
||||||
|
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*HttpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||||
|
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
|
||||||
|
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
||||||
|
cfg.HttpUser != cmpConf.HttpUser ||
|
||||||
|
cfg.HttpPwd != cmpConf.HttpPwd ||
|
||||||
|
len(cfg.Headers) != len(cmpConf.Headers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cfg.Headers {
|
||||||
|
if v2, ok := cmpConf.Headers[k]; !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if v != v2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||||
|
|
||||||
|
cfg.Locations = pMsg.Locations
|
||||||
|
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
|
||||||
|
cfg.HttpUser = pMsg.HttpUser
|
||||||
|
cfg.HttpPwd = pMsg.HttpPwd
|
||||||
|
cfg.Headers = pMsg.Headers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.UnmarshalFromIni(prefix, 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"]
|
||||||
|
cfg.Headers = make(map[string]string)
|
||||||
|
|
||||||
|
for k, v := range section {
|
||||||
|
if strings.HasPrefix(k, "header_") {
|
||||||
|
cfg.Headers[strings.TrimPrefix(k, "header_")] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||||
|
|
||||||
|
pMsg.Locations = cfg.Locations
|
||||||
|
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
|
||||||
|
pMsg.HttpUser = cfg.HttpUser
|
||||||
|
pMsg.HttpPwd = cfg.HttpPwd
|
||||||
|
pMsg.Headers = cfg.Headers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
|
||||||
|
if vhostHttpPort == 0 {
|
||||||
|
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||||
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPS
|
||||||
|
type HttpsProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
DomainConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*HttpsProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
|
||||||
|
if vhostHttpsPort == 0 {
|
||||||
|
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||||
|
}
|
||||||
|
if err = cfg.DomainConf.checkForSvr(); err != nil {
|
||||||
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// STCP
|
||||||
|
type StcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*StcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Role = section["role"]
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
err = fmt.Errorf("role should be 'server'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*XtcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Role = section["role"]
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
err = fmt.Errorf("role should be 'server'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) {
|
||||||
|
localPorts, errRet := util.ParseRangeNumbers(section["local_port"])
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"])
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(localPorts) != len(remotePorts) {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(localPorts) == 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sections = make(map[string]ini.Section)
|
||||||
|
for i, port := range localPorts {
|
||||||
|
subName := fmt.Sprintf("%s_%d", name, i)
|
||||||
|
subSection := copySection(section)
|
||||||
|
subSection["local_port"] = fmt.Sprintf("%d", port)
|
||||||
|
subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i])
|
||||||
|
sections[subName] = subSection
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if len(startProxy) is 0, start all
|
||||||
|
// otherwise just start proxies in startProxy map
|
||||||
|
func LoadAllConfFromIni(prefix string, content string, startProxy map[string]struct{}) (
|
||||||
|
proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) {
|
||||||
|
|
||||||
|
conf, errRet := ini.Load(strings.NewReader(content))
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
|
||||||
|
startAll := true
|
||||||
|
if len(startProxy) > 0 {
|
||||||
|
startAll = false
|
||||||
|
}
|
||||||
|
proxyConfs = make(map[string]ProxyConf)
|
||||||
|
visitorConfs = make(map[string]VisitorConf)
|
||||||
|
for name, section := range conf {
|
||||||
|
if name == "common" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, shouldStart := startProxy[name]
|
||||||
|
if !startAll && !shouldStart {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
subSections := make(map[string]ini.Section)
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, "range:") {
|
||||||
|
// range section
|
||||||
|
rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:"))
|
||||||
|
subSections, err = ParseRangeSection(rangePrefix, section)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subSections[name] = section
|
||||||
|
}
|
||||||
|
|
||||||
|
for subName, subSection := range subSections {
|
||||||
|
if subSection["role"] == "" {
|
||||||
|
subSection["role"] = "server"
|
||||||
|
}
|
||||||
|
role := subSection["role"]
|
||||||
|
if role == "server" {
|
||||||
|
cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxyConfs[prefix+subName] = cfg
|
||||||
|
} else if role == "visitor" {
|
||||||
|
cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConfs[prefix+subName] = cfg
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("role should be 'server' or 'visitor'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySection(section ini.Section) (out ini.Section) {
|
||||||
|
out = make(ini.Section)
|
||||||
|
for k, v := range section {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
316
models/config/server_common.go
Normal file
316
models/config/server_common.go
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// server global configure used for generate proxy conf used in frps
|
||||||
|
proxyBindAddr string
|
||||||
|
subDomainHost string
|
||||||
|
vhostHttpPort int
|
||||||
|
vhostHttpsPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitServerCfg(cfg *ServerCommonConf) {
|
||||||
|
proxyBindAddr = cfg.ProxyBindAddr
|
||||||
|
subDomainHost = cfg.SubDomainHost
|
||||||
|
vhostHttpPort = cfg.VhostHttpPort
|
||||||
|
vhostHttpsPort = cfg.VhostHttpsPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// common config
|
||||||
|
type ServerCommonConf struct {
|
||||||
|
BindAddr string `json:"bind_addr"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
|
KcpBindPort int `json:"kcp_bind_port"`
|
||||||
|
ProxyBindAddr string `json:"proxy_bind_addr"`
|
||||||
|
|
||||||
|
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||||
|
VhostHttpPort int `json:"vhost_http_port"`
|
||||||
|
|
||||||
|
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||||
|
VhostHttpsPort int `json:"vhost_https_port"`
|
||||||
|
|
||||||
|
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||||
|
|
||||||
|
DashboardAddr string `json:"dashboard_addr"`
|
||||||
|
|
||||||
|
// if DashboardPort equals 0, dashboard is not available
|
||||||
|
DashboardPort int `json:"dashboard_port"`
|
||||||
|
DashboardUser string `json:"dashboard_user"`
|
||||||
|
DashboardPwd string `json:"dashboard_pwd"`
|
||||||
|
AssetsDir string `json:"asserts_dir"`
|
||||||
|
LogFile string `json:"log_file"`
|
||||||
|
LogWay string `json:"log_way"` // console or file
|
||||||
|
LogLevel string `json:"log_level"`
|
||||||
|
LogMaxDays int64 `json:"log_max_days"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
SubDomainHost string `json:"subdomain_host"`
|
||||||
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
Custom404Page string `json:"custom_404_page"`
|
||||||
|
|
||||||
|
AllowPorts map[int]struct{}
|
||||||
|
MaxPoolCount int64 `json:"max_pool_count"`
|
||||||
|
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||||
|
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||||
|
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultServerConf() *ServerCommonConf {
|
||||||
|
return &ServerCommonConf{
|
||||||
|
BindAddr: "0.0.0.0",
|
||||||
|
BindPort: 7000,
|
||||||
|
BindUdpPort: 0,
|
||||||
|
KcpBindPort: 0,
|
||||||
|
ProxyBindAddr: "0.0.0.0",
|
||||||
|
VhostHttpPort: 0,
|
||||||
|
VhostHttpsPort: 0,
|
||||||
|
VhostHttpTimeout: 60,
|
||||||
|
DashboardAddr: "0.0.0.0",
|
||||||
|
DashboardPort: 0,
|
||||||
|
DashboardUser: "admin",
|
||||||
|
DashboardPwd: "admin",
|
||||||
|
AssetsDir: "",
|
||||||
|
LogFile: "console",
|
||||||
|
LogWay: "console",
|
||||||
|
LogLevel: "info",
|
||||||
|
LogMaxDays: 3,
|
||||||
|
Token: "",
|
||||||
|
SubDomainHost: "",
|
||||||
|
TcpMux: true,
|
||||||
|
AllowPorts: make(map[int]struct{}),
|
||||||
|
MaxPoolCount: 5,
|
||||||
|
MaxPortsPerClient: 0,
|
||||||
|
HeartBeatTimeout: 90,
|
||||||
|
UserConnTimeout: 10,
|
||||||
|
Custom404Page: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
|
||||||
|
cfg = defaultCfg
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = GetDefaultServerConf()
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := ini.Load(strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("parse ini conf file error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
v int64
|
||||||
|
)
|
||||||
|
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
|
||||||
|
cfg.BindAddr = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "bind_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid bind_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.BindPort = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.BindUdpPort = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.KcpBindPort = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
|
||||||
|
cfg.ProxyBindAddr = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.ProxyBindAddr = cfg.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.VhostHttpPort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.VhostHttpPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.VhostHttpsPort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.VhostHttpsPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||||
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if errRet != nil || v < 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.VhostHttpTimeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
|
||||||
|
cfg.DashboardAddr = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.DashboardAddr = cfg.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.DashboardPort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.DashboardPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
|
||||||
|
cfg.DashboardUser = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
|
||||||
|
cfg.DashboardPwd = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
|
||||||
|
cfg.AssetsDir = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_file"); ok {
|
||||||
|
cfg.LogFile = tmpStr
|
||||||
|
if cfg.LogFile == "console" {
|
||||||
|
cfg.LogWay = "console"
|
||||||
|
} else {
|
||||||
|
cfg.LogWay = "file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_level"); ok {
|
||||||
|
cfg.LogLevel = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
|
||||||
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
cfg.LogMaxDays = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Token, _ = conf.Get("common", "token")
|
||||||
|
|
||||||
|
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
|
||||||
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
|
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, port := range ports {
|
||||||
|
cfg.AllowPorts[int(port)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if v < 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.MaxPoolCount = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if v < 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.MaxPortsPerClient = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
|
||||||
|
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
|
||||||
|
cfg.TcpMux = false
|
||||||
|
} else {
|
||||||
|
cfg.TcpMux = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
|
||||||
|
cfg.Custom404Page = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ServerCommonConf) Check() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
64
models/config/value.go
Normal file
64
models/config/value.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
glbEnvs map[string]string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
glbEnvs = make(map[string]string)
|
||||||
|
envs := os.Environ()
|
||||||
|
for _, env := range envs {
|
||||||
|
kv := strings.Split(env, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
glbEnvs[kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
Envs map[string]string // environment vars
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValues() *Values {
|
||||||
|
return &Values{
|
||||||
|
Envs: glbEnvs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderContent(in string) (out string, err error) {
|
||||||
|
tmpl, errRet := template.New("frp").Parse(in)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBufferString("")
|
||||||
|
v := GetValues()
|
||||||
|
err = tmpl.Execute(buffer, v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = buffer.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRenderedConfFromFile(path string) (out string, err error) {
|
||||||
|
var b []byte
|
||||||
|
b, err = ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := string(b)
|
||||||
|
|
||||||
|
out, err = RenderContent(content)
|
||||||
|
return
|
||||||
|
}
|
||||||
213
models/config/visitor.go
Normal file
213
models/config/visitor.go
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
|
||||||
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
visitorConfTypeMap map[string]reflect.Type
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
visitorConfTypeMap = make(map[string]reflect.Type)
|
||||||
|
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
|
||||||
|
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisitorConf interface {
|
||||||
|
GetBaseInfo() *BaseVisitorConf
|
||||||
|
Compare(cmp VisitorConf) bool
|
||||||
|
UnmarshalFromIni(prefix string, name string, section ini.Section) error
|
||||||
|
Check() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorConfByType(cfgType string) VisitorConf {
|
||||||
|
v, ok := visitorConfTypeMap[cfgType]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cfg := reflect.New(v).Interface().(VisitorConf)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) {
|
||||||
|
cfgType := section["type"]
|
||||||
|
if cfgType == "" {
|
||||||
|
err = fmt.Errorf("visitor [%s] type shouldn't be empty", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg = NewVisitorConfByType(cfgType)
|
||||||
|
if cfg == nil {
|
||||||
|
err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cfg.Check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitorConf struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
ProxyType string `json:"proxy_type"`
|
||||||
|
UseEncryption bool `json:"use_encryption"`
|
||||||
|
UseCompression bool `json:"use_compression"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
BindAddr string `json:"bind_addr"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool {
|
||||||
|
if cfg.ProxyName != cmp.ProxyName ||
|
||||||
|
cfg.ProxyType != cmp.ProxyType ||
|
||||||
|
cfg.UseEncryption != cmp.UseEncryption ||
|
||||||
|
cfg.UseCompression != cmp.UseCompression ||
|
||||||
|
cfg.Role != cmp.Role ||
|
||||||
|
cfg.Sk != cmp.Sk ||
|
||||||
|
cfg.ServerName != cmp.ServerName ||
|
||||||
|
cfg.BindAddr != cmp.BindAddr ||
|
||||||
|
cfg.BindPort != cmp.BindPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseVisitorConf) check() (err error) {
|
||||||
|
if cfg.Role != "visitor" {
|
||||||
|
err = fmt.Errorf("invalid role")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.BindAddr == "" {
|
||||||
|
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.BindPort <= 0 {
|
||||||
|
err = fmt.Errorf("bind_port is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
var (
|
||||||
|
tmpStr string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
cfg.ProxyName = prefix + name
|
||||||
|
cfg.ProxyType = section["type"]
|
||||||
|
|
||||||
|
if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" {
|
||||||
|
cfg.UseEncryption = true
|
||||||
|
}
|
||||||
|
if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" {
|
||||||
|
cfg.UseCompression = true
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Role = section["role"]
|
||||||
|
if cfg.Role != "visitor" {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||||
|
}
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
cfg.ServerName = prefix + section["server_name"]
|
||||||
|
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||||
|
cfg.BindAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = section["bind_port"]; ok {
|
||||||
|
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StcpVisitorConf struct {
|
||||||
|
BaseVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*StcpVisitorConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpVisitorConf) Check() (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpVisitorConf struct {
|
||||||
|
BaseVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*XtcpVisitorConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpVisitorConf) Check() (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -12,19 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package config
|
package consts
|
||||||
|
|
||||||
type BaseConf struct {
|
var (
|
||||||
Name string
|
// proxy status
|
||||||
AuthToken string
|
Idle string = "idle"
|
||||||
Type string
|
Working string = "working"
|
||||||
UseEncryption bool
|
Closed string = "closed"
|
||||||
UseGzip bool
|
Online string = "online"
|
||||||
PrivilegeMode bool
|
Offline string = "offline"
|
||||||
PrivilegeToken string
|
|
||||||
PoolCount int64
|
// proxy type
|
||||||
HostHeaderRewrite string
|
TcpProxy string = "tcp"
|
||||||
HttpUserName string
|
UdpProxy string = "udp"
|
||||||
HttpPassWord string
|
HttpProxy string = "http"
|
||||||
SubDomain string
|
HttpsProxy string = "https"
|
||||||
}
|
StcpProxy string = "stcp"
|
||||||
|
XtcpProxy string = "xtcp"
|
||||||
|
)
|
||||||
@@ -12,30 +12,13 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package consts
|
package errors
|
||||||
|
|
||||||
// server status
|
import (
|
||||||
const (
|
"errors"
|
||||||
Idle = iota
|
|
||||||
Working
|
|
||||||
Closed
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
StatusStr = []string{
|
ErrMsgType = errors.New("message type error")
|
||||||
"idle",
|
ErrCtlClosed = errors.New("control is closed")
|
||||||
"working",
|
|
||||||
"closed",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// msg type
|
|
||||||
const (
|
|
||||||
NewCtlConn = iota
|
|
||||||
NewWorkConn
|
|
||||||
NoticeUserConn
|
|
||||||
NewCtlConnRes
|
|
||||||
HeartbeatReq
|
|
||||||
HeartbeatRes
|
|
||||||
NewWorkConnUdp
|
|
||||||
)
|
)
|
||||||
46
models/msg/ctl.go
Normal file
46
models/msg/ctl.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// 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 msg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
jsonMsg "github.com/fatedier/golib/msg/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message = jsonMsg.Message
|
||||||
|
|
||||||
|
var (
|
||||||
|
msgCtl *jsonMsg.MsgCtl
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
msgCtl = jsonMsg.NewMsgCtl()
|
||||||
|
for typeByte, msg := range msgTypeMap {
|
||||||
|
msgCtl.RegisterMsg(typeByte, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadMsg(c io.Reader) (msg Message, err error) {
|
||||||
|
return msgCtl.ReadMsg(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadMsgInto(c io.Reader, msg Message) (err error) {
|
||||||
|
return msgCtl.ReadMsgInto(c, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteMsg(c io.Writer, msg interface{}) (err error) {
|
||||||
|
return msgCtl.WriteMsg(c, msg)
|
||||||
|
}
|
||||||
183
models/msg/msg.go
Normal file
183
models/msg/msg.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeLogin = 'o'
|
||||||
|
TypeLoginResp = '1'
|
||||||
|
TypeNewProxy = 'p'
|
||||||
|
TypeNewProxyResp = '2'
|
||||||
|
TypeCloseProxy = 'c'
|
||||||
|
TypeNewWorkConn = 'w'
|
||||||
|
TypeReqWorkConn = 'r'
|
||||||
|
TypeStartWorkConn = 's'
|
||||||
|
TypeNewVisitorConn = 'v'
|
||||||
|
TypeNewVisitorConnResp = '3'
|
||||||
|
TypePing = 'h'
|
||||||
|
TypePong = '4'
|
||||||
|
TypeUdpPacket = 'u'
|
||||||
|
TypeNatHoleVisitor = 'i'
|
||||||
|
TypeNatHoleClient = 'n'
|
||||||
|
TypeNatHoleResp = 'm'
|
||||||
|
TypeNatHoleClientDetectOK = 'd'
|
||||||
|
TypeNatHoleSid = '5'
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
msgTypeMap = map[byte]interface{}{
|
||||||
|
TypeLogin: Login{},
|
||||||
|
TypeLoginResp: LoginResp{},
|
||||||
|
TypeNewProxy: NewProxy{},
|
||||||
|
TypeNewProxyResp: NewProxyResp{},
|
||||||
|
TypeCloseProxy: CloseProxy{},
|
||||||
|
TypeNewWorkConn: NewWorkConn{},
|
||||||
|
TypeReqWorkConn: ReqWorkConn{},
|
||||||
|
TypeStartWorkConn: StartWorkConn{},
|
||||||
|
TypeNewVisitorConn: NewVisitorConn{},
|
||||||
|
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||||
|
TypePing: Ping{},
|
||||||
|
TypePong: Pong{},
|
||||||
|
TypeUdpPacket: UdpPacket{},
|
||||||
|
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||||
|
TypeNatHoleClient: NatHoleClient{},
|
||||||
|
TypeNatHoleResp: NatHoleResp{},
|
||||||
|
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
||||||
|
TypeNatHoleSid: NatHoleSid{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
ServerUdpPort int `json:"server_udp_port"`
|
||||||
|
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"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
GroupKey string `json:"group_key"`
|
||||||
|
|
||||||
|
// tcp and udp only
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
|
||||||
|
// http and https only
|
||||||
|
CustomDomains []string `json:"custom_domains"`
|
||||||
|
SubDomain string `json:"subdomain"`
|
||||||
|
Locations []string `json:"locations"`
|
||||||
|
HttpUser string `json:"http_user"`
|
||||||
|
HttpPwd string `json:"http_pwd"`
|
||||||
|
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
|
||||||
|
// stcp
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewProxyResp struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
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"`
|
||||||
|
SrcAddr string `json:"src_addr"`
|
||||||
|
DstAddr string `json:"dst_addr"`
|
||||||
|
SrcPort uint16 `json:"src_port"`
|
||||||
|
DstPort uint16 `json:"dst_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewVisitorConn struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SignKey string `json:"sign_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
UseEncryption bool `json:"use_encryption"`
|
||||||
|
UseCompression bool `json:"use_compression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewVisitorConnResp struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ping struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pong struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type UdpPacket struct {
|
||||||
|
Content string `json:"c"`
|
||||||
|
LocalAddr *net.UDPAddr `json:"l"`
|
||||||
|
RemoteAddr *net.UDPAddr `json:"r"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleVisitor struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SignKey string `json:"sign_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClient struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleResp struct {
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
VisitorAddr string `json:"visitor_addr"`
|
||||||
|
ClientAddr string `json:"client_addr"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClientDetectOK struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSid struct {
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
}
|
||||||
212
models/nathole/nathole.go
Normal file
212
models/nathole/nathole.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package nathole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timeout seconds.
|
||||||
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
type SidRequest struct {
|
||||||
|
Sid string
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleController struct {
|
||||||
|
listener *net.UDPConn
|
||||||
|
|
||||||
|
clientCfgs map[string]*NatHoleClientCfg
|
||||||
|
sessions map[string]*NatHoleSession
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lconn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nc = &NatHoleController{
|
||||||
|
listener: lconn,
|
||||||
|
clientCfgs: make(map[string]*NatHoleClientCfg),
|
||||||
|
sessions: make(map[string]*NatHoleSession),
|
||||||
|
}
|
||||||
|
return nc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||||
|
clientCfg := &NatHoleClientCfg{
|
||||||
|
Name: name,
|
||||||
|
Sk: sk,
|
||||||
|
SidCh: make(chan *SidRequest),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
nc.clientCfgs[name] = clientCfg
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return clientCfg.SidCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) CloseClient(name string) {
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
delete(nc.clientCfgs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) Run() {
|
||||||
|
for {
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("nat hole listener read from udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bytes.NewReader(buf[:n])
|
||||||
|
rawMsg, err := msg.ReadMsg(rd)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("read nat hole message error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.NatHoleVisitor:
|
||||||
|
go nc.HandleVisitor(m, raddr)
|
||||||
|
case *msg.NatHoleClient:
|
||||||
|
go nc.HandleClient(m, raddr)
|
||||||
|
default:
|
||||||
|
log.Trace("error nat hole message type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenSid() string {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
id, _ := util.RandId()
|
||||||
|
return fmt.Sprintf("%d%s", t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
||||||
|
sid := nc.GenSid()
|
||||||
|
session := &NatHoleSession{
|
||||||
|
Sid: sid,
|
||||||
|
VisitorAddr: raddr,
|
||||||
|
NotifyCh: make(chan struct{}, 0),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
||||||
|
log.Debug(errInfo)
|
||||||
|
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
|
||||||
|
log.Debug(errInfo)
|
||||||
|
nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nc.sessions[sid] = session
|
||||||
|
nc.mu.Unlock()
|
||||||
|
log.Trace("handle visitor message, sid [%s]", sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
nc.mu.Lock()
|
||||||
|
delete(nc.sessions, sid)
|
||||||
|
nc.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
clientCfg.SidCh <- &SidRequest{
|
||||||
|
Sid: sid,
|
||||||
|
NotifyCh: session.NotifyCh,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait client connections.
|
||||||
|
select {
|
||||||
|
case <-session.NotifyCh:
|
||||||
|
resp := nc.GenNatHoleResponse(session, "")
|
||||||
|
log.Trace("send nat hole response to visitor")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
||||||
|
nc.mu.RLock()
|
||||||
|
session, ok := nc.sessions[m.Sid]
|
||||||
|
nc.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||||
|
session.ClientAddr = raddr
|
||||||
|
|
||||||
|
resp := nc.GenNatHoleResponse(session, "")
|
||||||
|
log.Trace("send nat hole response to client")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
|
||||||
|
var (
|
||||||
|
sid string
|
||||||
|
visitorAddr string
|
||||||
|
clientAddr string
|
||||||
|
)
|
||||||
|
if session != nil {
|
||||||
|
sid = session.Sid
|
||||||
|
visitorAddr = session.VisitorAddr.String()
|
||||||
|
clientAddr = session.ClientAddr.String()
|
||||||
|
}
|
||||||
|
m := &msg.NatHoleResp{
|
||||||
|
Sid: sid,
|
||||||
|
VisitorAddr: visitorAddr,
|
||||||
|
ClientAddr: clientAddr,
|
||||||
|
Error: errInfo,
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
err := msg.WriteMsg(b, m)
|
||||||
|
if err != nil {
|
||||||
|
return []byte("")
|
||||||
|
}
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSession struct {
|
||||||
|
Sid string
|
||||||
|
VisitorAddr *net.UDPAddr
|
||||||
|
ClientAddr *net.UDPAddr
|
||||||
|
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClientCfg struct {
|
||||||
|
Name string
|
||||||
|
Sk string
|
||||||
|
SidCh chan *SidRequest
|
||||||
|
}
|
||||||
243
models/plugin/http_proxy.go
Normal file
243
models/plugin/http_proxy.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
// Copyright 2017 frp team
|
||||||
|
//
|
||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
gnet "github.com/fatedier/golib/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginHttpProxy = "http_proxy"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpProxy struct {
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
AuthUser string
|
||||||
|
AuthPasswd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
|
||||||
|
user := params["plugin_http_user"]
|
||||||
|
passwd := params["plugin_http_passwd"]
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
hp := &HttpProxy{
|
||||||
|
l: listener,
|
||||||
|
AuthUser: user,
|
||||||
|
AuthPasswd: passwd,
|
||||||
|
}
|
||||||
|
|
||||||
|
hp.s = &http.Server{
|
||||||
|
Handler: hp,
|
||||||
|
}
|
||||||
|
|
||||||
|
go hp.s.Serve(listener)
|
||||||
|
return hp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) Name() string {
|
||||||
|
return PluginHttpProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
|
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||||
|
firstBytes := make([]byte, 7)
|
||||||
|
_, err := rd.Read(firstBytes)
|
||||||
|
if err != nil {
|
||||||
|
wrapConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToUpper(string(firstBytes)) == "CONNECT" {
|
||||||
|
bufRd := bufio.NewReader(sc)
|
||||||
|
request, err := http.ReadRequest(bufRd)
|
||||||
|
if err != nil {
|
||||||
|
wrapConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hp.l.PutConn(sc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) Close() error {
|
||||||
|
hp.s.Close()
|
||||||
|
hp.l.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if ok := hp.Auth(req); !ok {
|
||||||
|
rw.Header().Set("Proxy-Authenticate", "Basic")
|
||||||
|
rw.WriteHeader(http.StatusProxyAuthRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == http.MethodConnect {
|
||||||
|
// deprecated
|
||||||
|
// Connect request is handled in Handle function.
|
||||||
|
hp.ConnectHandler(rw, req)
|
||||||
|
} else {
|
||||||
|
hp.HttpHandler(rw, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
removeProxyHeaders(req)
|
||||||
|
|
||||||
|
resp, err := http.DefaultTransport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
copyHeaders(rw.Header(), resp.Header)
|
||||||
|
rw.WriteHeader(resp.StatusCode)
|
||||||
|
|
||||||
|
_, err = io.Copy(rw, resp.Body)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
// Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here,
|
||||||
|
// we may always get i/o timeout error.
|
||||||
|
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
hj, ok := rw.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, _, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remote, err := net.Dial("tcp", req.URL.Host)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, "Failed", http.StatusBadRequest)
|
||||||
|
client.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
|
go frpIo.Join(remote, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) Auth(req *http.Request) bool {
|
||||||
|
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
s := strings.SplitN(req.Header.Get("Proxy-Authorization"), " ", 2)
|
||||||
|
if len(s) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pair := strings.SplitN(string(b), ":", 2)
|
||||||
|
if len(pair) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair[0] != hp.AuthUser || pair[1] != hp.AuthPasswd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
|
||||||
|
defer rwc.Close()
|
||||||
|
if ok := hp.Auth(req); !ok {
|
||||||
|
res := getBadResponse()
|
||||||
|
res.Write(rwc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remote, err := net.Dial("tcp", req.URL.Host)
|
||||||
|
if err != nil {
|
||||||
|
res := &http.Response{
|
||||||
|
StatusCode: 400,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
}
|
||||||
|
res.Write(rwc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
|
frpIo.Join(remote, rwc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyHeaders(dst, src http.Header) {
|
||||||
|
for key, values := range src {
|
||||||
|
for _, value := range values {
|
||||||
|
dst.Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeProxyHeaders(req *http.Request) {
|
||||||
|
req.RequestURI = ""
|
||||||
|
req.Header.Del("Proxy-Connection")
|
||||||
|
req.Header.Del("Connection")
|
||||||
|
req.Header.Del("Proxy-Authenticate")
|
||||||
|
req.Header.Del("Proxy-Authorization")
|
||||||
|
req.Header.Del("TE")
|
||||||
|
req.Header.Del("Trailers")
|
||||||
|
req.Header.Del("Transfer-Encoding")
|
||||||
|
req.Header.Del("Upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBadResponse() *http.Response {
|
||||||
|
header := make(map[string][]string)
|
||||||
|
header["Proxy-Authenticate"] = []string{"Basic"}
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "407 Not authorized",
|
||||||
|
StatusCode: 407,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
114
models/plugin/https2http.go
Normal file
114
models/plugin/https2http.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2019 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginHTTPS2HTTP = "https2http"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPS2HTTPPlugin struct {
|
||||||
|
crtPath string
|
||||||
|
keyPath string
|
||||||
|
hostHeaderRewrite string
|
||||||
|
localAddr string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||||
|
crtPath := params["plugin_crt_path"]
|
||||||
|
keyPath := params["plugin_key_path"]
|
||||||
|
localAddr := params["plugin_local_addr"]
|
||||||
|
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||||
|
|
||||||
|
if crtPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||||
|
}
|
||||||
|
if keyPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_key_path is required")
|
||||||
|
}
|
||||||
|
if localAddr == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
p := &HTTPS2HTTPPlugin{
|
||||||
|
crtPath: crtPath,
|
||||||
|
keyPath: keyPath,
|
||||||
|
localAddr: localAddr,
|
||||||
|
hostHeaderRewrite: hostHeaderRewrite,
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = p.localAddr
|
||||||
|
if p.hostHeaderRewrite != "" {
|
||||||
|
req.Host = p.hostHeaderRewrite
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: rp,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := p.genTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
|
}
|
||||||
|
ln := tls.NewListener(listener, tlsConfig)
|
||||||
|
|
||||||
|
go p.s.Serve(ln)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||||
|
return PluginHTTPS2HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
92
models/plugin/plugin.go
Normal file
92
models/plugin/plugin.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Creators is used for create plugins to handle connections.
|
||||||
|
var creators = make(map[string]CreatorFn)
|
||||||
|
|
||||||
|
// params has prefix "plugin_"
|
||||||
|
type CreatorFn func(params map[string]string) (Plugin, error)
|
||||||
|
|
||||||
|
func Register(name string, fn CreatorFn) {
|
||||||
|
creators[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||||
|
if fn, ok := creators[name]; ok {
|
||||||
|
p, err = fn(params)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("plugin [%s] is not registered", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
conns chan net.Conn
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyListener() *Listener {
|
||||||
|
return &Listener{
|
||||||
|
conns: make(chan net.Conn, 64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
|
conn, ok := <-l.conns
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("listener closed")
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) PutConn(conn net.Conn) error {
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
l.conns <- conn
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
if !l.closed {
|
||||||
|
close(l.conns)
|
||||||
|
l.closed = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Addr() net.Addr {
|
||||||
|
return (*net.TCPAddr)(nil)
|
||||||
|
}
|
||||||
68
models/plugin/socks5.go
Normal file
68
models/plugin/socks5.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
gosocks5 "github.com/armon/go-socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginSocks5 = "socks5"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginSocks5, NewSocks5Plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Socks5Plugin struct {
|
||||||
|
Server *gosocks5.Server
|
||||||
|
|
||||||
|
user string
|
||||||
|
passwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||||
|
user := params["plugin_user"]
|
||||||
|
passwd := params["plugin_passwd"]
|
||||||
|
|
||||||
|
cfg := &gosocks5.Config{
|
||||||
|
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
||||||
|
}
|
||||||
|
if user != "" || passwd != "" {
|
||||||
|
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
|
||||||
|
}
|
||||||
|
sp := &Socks5Plugin{}
|
||||||
|
sp.Server, err = gosocks5.New(cfg)
|
||||||
|
p = sp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
defer conn.Close()
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
sp.Server.ServeConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Name() string {
|
||||||
|
return PluginSocks5
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
88
models/plugin/static_file.go
Normal file
88
models/plugin/static_file.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginStaticFile = "static_file"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginStaticFile, NewStaticFilePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaticFilePlugin struct {
|
||||||
|
localPath string
|
||||||
|
stripPrefix string
|
||||||
|
httpUser string
|
||||||
|
httpPasswd string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||||
|
localPath := params["plugin_local_path"]
|
||||||
|
stripPrefix := params["plugin_strip_prefix"]
|
||||||
|
httpUser := params["plugin_http_user"]
|
||||||
|
httpPasswd := params["plugin_http_passwd"]
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
sp := &StaticFilePlugin{
|
||||||
|
localPath: localPath,
|
||||||
|
stripPrefix: stripPrefix,
|
||||||
|
httpUser: httpUser,
|
||||||
|
httpPasswd: httpPasswd,
|
||||||
|
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
var prefix string
|
||||||
|
if stripPrefix != "" {
|
||||||
|
prefix = "/" + stripPrefix + "/"
|
||||||
|
} else {
|
||||||
|
prefix = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.Use(frpNet.NewHttpAuthMiddleware(httpUser, httpPasswd).Middleware)
|
||||||
|
router.PathPrefix(prefix).Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
||||||
|
sp.s = &http.Server{
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
go sp.s.Serve(listener)
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
sp.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Name() string {
|
||||||
|
return PluginStaticFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Close() error {
|
||||||
|
sp.s.Close()
|
||||||
|
sp.l.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
74
models/plugin/unix_domain_socket.go
Normal file
74
models/plugin/unix_domain_socket.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnixDomainSocketPlugin struct {
|
||||||
|
UnixAddr *net.UnixAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||||
|
unixPath, ok := params["plugin_unix_path"]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("plugin_unix_path not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p = &UnixDomainSocketPlugin{
|
||||||
|
UnixAddr: unixAddr,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(extraBufToLocal) > 0 {
|
||||||
|
localConn.Write(extraBufToLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(localConn, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||||
|
return PluginUnixDomainSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uds *UnixDomainSocketPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
136
models/proto/udp/udp.go
Normal file
136
models/proto/udp/udp.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// 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 udp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
|
||||||
|
return &msg.UdpPacket{
|
||||||
|
Content: base64.StdEncoding.EncodeToString(buf),
|
||||||
|
LocalAddr: laddr,
|
||||||
|
RemoteAddr: raddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContent(m *msg.UdpPacket) (buf []byte, err error) {
|
||||||
|
buf, err = base64.StdEncoding.DecodeString(m.Content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) {
|
||||||
|
// read
|
||||||
|
go func() {
|
||||||
|
for udpMsg := range readCh {
|
||||||
|
buf, err := GetContent(udpMsg)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
udpConn.WriteToUDP(buf, udpMsg.RemoteAddr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// write
|
||||||
|
buf := pool.GetBuf(1500)
|
||||||
|
defer pool.PutBuf(buf)
|
||||||
|
for {
|
||||||
|
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
udpConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// buf[:n] will be encoded to string, so the bytes can be reused
|
||||||
|
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
|
||||||
|
select {
|
||||||
|
case sendCh <- udpMsg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||||
|
var (
|
||||||
|
mu sync.RWMutex
|
||||||
|
)
|
||||||
|
udpConnMap := make(map[string]*net.UDPConn)
|
||||||
|
|
||||||
|
// read from dstAddr and write to sendCh
|
||||||
|
writerFn := func(raddr *net.UDPAddr, udpConn *net.UDPConn) {
|
||||||
|
addr := raddr.String()
|
||||||
|
defer func() {
|
||||||
|
mu.Lock()
|
||||||
|
delete(udpConnMap, addr)
|
||||||
|
mu.Unlock()
|
||||||
|
udpConn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf := pool.GetBuf(1500)
|
||||||
|
for {
|
||||||
|
udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
n, _, err := udpConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
udpMsg := NewUdpPacket(buf[:n], nil, raddr)
|
||||||
|
if err = errors.PanicToError(func() {
|
||||||
|
select {
|
||||||
|
case sendCh <- udpMsg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read from readCh
|
||||||
|
go func() {
|
||||||
|
for udpMsg := range readCh {
|
||||||
|
buf, err := GetContent(udpMsg)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
|
||||||
|
if !ok {
|
||||||
|
udpConn, err = net.DialUDP("udp", nil, dstAddr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
_, err = udpConn.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
udpConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
go writerFn(udpMsg.RemoteAddr, udpConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
18
models/proto/udp/udp_test.go
Normal file
18
models/proto/udp/udp_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package udp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUdpPacket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
buf := []byte("hello world")
|
||||||
|
udpMsg := NewUdpPacket(buf, nil, nil)
|
||||||
|
|
||||||
|
newBuf, err := GetContent(udpMsg)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.EqualValues(buf, newBuf)
|
||||||
|
}
|
||||||
@@ -14,8 +14,8 @@ make -f ./Makefile.cross-compiles
|
|||||||
rm -rf ./packages
|
rm -rf ./packages
|
||||||
mkdir ./packages
|
mkdir ./packages
|
||||||
|
|
||||||
os_all='linux windows darwin'
|
os_all='linux windows darwin freebsd'
|
||||||
arch_all='386 amd64 arm mips64 mips64le'
|
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
|
||||||
|
|
||||||
for os in $os_all; do
|
for os in $os_all; do
|
||||||
for arch in $arch_all; do
|
for arch in $arch_all; do
|
||||||
@@ -44,7 +44,7 @@ for os in $os_all; do
|
|||||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||||
fi
|
fi
|
||||||
cp ./LICENSE ${frp_path}
|
cp ./LICENSE ${frp_path}
|
||||||
cp ./conf/* ${frp_path}
|
cp -rf ./conf/* ${frp_path}
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
cd ./packages
|
cd ./packages
|
||||||
|
|||||||
494
server/control.go
Normal file
494
server/control.go
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
// 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
frpErr "github.com/fatedier/frp/models/errors"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/server/controller"
|
||||||
|
"github.com/fatedier/frp/server/proxy"
|
||||||
|
"github.com/fatedier/frp/server/stats"
|
||||||
|
"github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
|
"github.com/fatedier/golib/crypto"
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ControlManager struct {
|
||||||
|
// controls indexed by run id
|
||||||
|
ctlsByRunId map[string]*Control
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControlManager() *ControlManager {
|
||||||
|
return &ControlManager{
|
||||||
|
ctlsByRunId: make(map[string]*Control),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
|
||||||
|
cm.mu.Lock()
|
||||||
|
defer cm.mu.Unlock()
|
||||||
|
|
||||||
|
oldCtl, ok := cm.ctlsByRunId[runId]
|
||||||
|
if ok {
|
||||||
|
oldCtl.Replaced(ctl)
|
||||||
|
}
|
||||||
|
cm.ctlsByRunId[runId] = ctl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should make sure if it's the same control to prevent delete a new one
|
||||||
|
func (cm *ControlManager) Del(runId string, ctl *Control) {
|
||||||
|
cm.mu.Lock()
|
||||||
|
defer cm.mu.Unlock()
|
||||||
|
if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl {
|
||||||
|
delete(cm.ctlsByRunId, runId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
||||||
|
cm.mu.RLock()
|
||||||
|
defer cm.mu.RUnlock()
|
||||||
|
ctl, ok = cm.ctlsByRunId[runId]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Control struct {
|
||||||
|
// all resource managers and controllers
|
||||||
|
rc *controller.ResourceController
|
||||||
|
|
||||||
|
// proxy manager
|
||||||
|
pxyManager *proxy.ProxyManager
|
||||||
|
|
||||||
|
// stats collector to store stats info of clients and proxies
|
||||||
|
statsCollector stats.Collector
|
||||||
|
|
||||||
|
// login message
|
||||||
|
loginMsg *msg.Login
|
||||||
|
|
||||||
|
// control connection
|
||||||
|
conn net.Conn
|
||||||
|
|
||||||
|
// put a message in this channel to send it over control connection to client
|
||||||
|
sendCh chan (msg.Message)
|
||||||
|
|
||||||
|
// read from this channel to get the next message sent by client
|
||||||
|
readCh chan (msg.Message)
|
||||||
|
|
||||||
|
// work connections
|
||||||
|
workConnCh chan net.Conn
|
||||||
|
|
||||||
|
// proxies in one client
|
||||||
|
proxies map[string]proxy.Proxy
|
||||||
|
|
||||||
|
// pool count
|
||||||
|
poolCount int
|
||||||
|
|
||||||
|
// ports used, for limitations
|
||||||
|
portsUsedNum int
|
||||||
|
|
||||||
|
// last time got the Ping message
|
||||||
|
lastPing time.Time
|
||||||
|
|
||||||
|
// A new run id will be generated when a new client login.
|
||||||
|
// If run id got from login message has same run id, it means it's the same client, so we can
|
||||||
|
// replace old controller instantly.
|
||||||
|
runId string
|
||||||
|
|
||||||
|
// control status
|
||||||
|
status string
|
||||||
|
|
||||||
|
readerShutdown *shutdown.Shutdown
|
||||||
|
writerShutdown *shutdown.Shutdown
|
||||||
|
managerShutdown *shutdown.Shutdown
|
||||||
|
allShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
|
||||||
|
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
||||||
|
|
||||||
|
return &Control{
|
||||||
|
rc: rc,
|
||||||
|
pxyManager: pxyManager,
|
||||||
|
statsCollector: statsCollector,
|
||||||
|
conn: ctlConn,
|
||||||
|
loginMsg: loginMsg,
|
||||||
|
sendCh: make(chan msg.Message, 10),
|
||||||
|
readCh: make(chan msg.Message, 10),
|
||||||
|
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
||||||
|
proxies: make(map[string]proxy.Proxy),
|
||||||
|
poolCount: loginMsg.PoolCount,
|
||||||
|
portsUsedNum: 0,
|
||||||
|
lastPing: time.Now(),
|
||||||
|
runId: loginMsg.RunId,
|
||||||
|
status: consts.Working,
|
||||||
|
readerShutdown: shutdown.New(),
|
||||||
|
writerShutdown: shutdown.New(),
|
||||||
|
managerShutdown: shutdown.New(),
|
||||||
|
allShutdown: shutdown.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start send a login success message to client and start working.
|
||||||
|
func (ctl *Control) Start() {
|
||||||
|
loginRespMsg := &msg.LoginResp{
|
||||||
|
Version: version.Full(),
|
||||||
|
RunId: ctl.runId,
|
||||||
|
ServerUdpPort: g.GlbServerCfg.BindUdpPort,
|
||||||
|
Error: "",
|
||||||
|
}
|
||||||
|
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
|
|
||||||
|
go ctl.writer()
|
||||||
|
for i := 0; i < ctl.poolCount; i++ {
|
||||||
|
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||||
|
}
|
||||||
|
|
||||||
|
go ctl.manager()
|
||||||
|
go ctl.reader()
|
||||||
|
go ctl.stoper()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) RegisterWorkConn(conn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case ctl.workConnCh <- conn:
|
||||||
|
ctl.conn.Debug("new work connection registered")
|
||||||
|
default:
|
||||||
|
ctl.conn.Debug("work connection pool is full, discarding")
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When frps get one user connection, we get one work connection from the pool and return it.
|
||||||
|
// If no workConn available in the pool, send message to frpc to get one or more
|
||||||
|
// and wait until it is available.
|
||||||
|
// return an error if wait timeout
|
||||||
|
func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
// get a work connection from the pool
|
||||||
|
select {
|
||||||
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
|
if !ok {
|
||||||
|
err = frpErr.ErrCtlClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctl.conn.Debug("get work connection from pool")
|
||||||
|
default:
|
||||||
|
// no work connections available in the poll, send message to frpc to get more
|
||||||
|
err = errors.PanicToError(func() {
|
||||||
|
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctl.conn.Error("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
|
if !ok {
|
||||||
|
err = frpErr.ErrCtlClosed
|
||||||
|
ctl.conn.Warn("no work connections avaiable, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
|
||||||
|
err = fmt.Errorf("timeout trying to get work connection")
|
||||||
|
ctl.conn.Warn("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we get a work connection from pool, replace it with a new one.
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
|
ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
|
||||||
|
ctl.runId = ""
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) writer() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer ctl.allShutdown.Start()
|
||||||
|
defer ctl.writerShutdown.Done()
|
||||||
|
|
||||||
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
ctl.conn.Error("crypto new writer error: %v", err)
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if m, ok := <-ctl.sendCh; !ok {
|
||||||
|
ctl.conn.Info("control writer is closing")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||||
|
ctl.conn.Warn("write message to control connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) reader() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer ctl.allShutdown.Start()
|
||||||
|
defer ctl.readerShutdown.Done()
|
||||||
|
|
||||||
|
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
|
||||||
|
for {
|
||||||
|
if m, err := msg.ReadMsg(encReader); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
ctl.conn.Debug("control connection closed")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ctl.conn.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctl.readCh <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) stoper() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctl.allShutdown.WaitStart()
|
||||||
|
|
||||||
|
close(ctl.readCh)
|
||||||
|
ctl.managerShutdown.WaitDone()
|
||||||
|
|
||||||
|
close(ctl.sendCh)
|
||||||
|
ctl.writerShutdown.WaitDone()
|
||||||
|
|
||||||
|
ctl.conn.Close()
|
||||||
|
ctl.readerShutdown.WaitDone()
|
||||||
|
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
|
||||||
|
close(ctl.workConnCh)
|
||||||
|
for workConn := range ctl.workConnCh {
|
||||||
|
workConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pxy := range ctl.proxies {
|
||||||
|
pxy.Close()
|
||||||
|
ctl.pxyManager.Del(pxy.GetName())
|
||||||
|
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
||||||
|
Name: pxy.GetName(),
|
||||||
|
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl.allShutdown.Done()
|
||||||
|
ctl.conn.Info("client exit success")
|
||||||
|
|
||||||
|
ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// block until Control closed
|
||||||
|
func (ctl *Control) WaitClosed() {
|
||||||
|
ctl.allShutdown.WaitDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) manager() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
ctl.conn.Error("panic error: %v", err)
|
||||||
|
ctl.conn.Error(string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer ctl.allShutdown.Start()
|
||||||
|
defer ctl.managerShutdown.Done()
|
||||||
|
|
||||||
|
heartbeat := time.NewTicker(time.Second)
|
||||||
|
defer heartbeat.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-heartbeat.C:
|
||||||
|
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
|
||||||
|
ctl.conn.Warn("heartbeat timeout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case rawMsg, ok := <-ctl.readCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.NewProxy:
|
||||||
|
// register proxy in this control
|
||||||
|
remoteAddr, err := ctl.RegisterProxy(m)
|
||||||
|
resp := &msg.NewProxyResp{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = err.Error()
|
||||||
|
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
|
||||||
|
} else {
|
||||||
|
resp.RemoteAddr = remoteAddr
|
||||||
|
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
|
||||||
|
ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
|
||||||
|
Name: m.ProxyName,
|
||||||
|
ProxyType: m.ProxyType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctl.sendCh <- resp
|
||||||
|
case *msg.CloseProxy:
|
||||||
|
ctl.CloseProxy(m)
|
||||||
|
ctl.conn.Info("close proxy [%s] success", m.ProxyName)
|
||||||
|
case *msg.Ping:
|
||||||
|
ctl.lastPing = time.Now()
|
||||||
|
ctl.conn.Debug("receive heartbeat")
|
||||||
|
ctl.sendCh <- &msg.Pong{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
|
var pxyConf config.ProxyConf
|
||||||
|
// Load configures from NewProxy message and check.
|
||||||
|
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProxy will return a interface Proxy.
|
||||||
|
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||||
|
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
|
||||||
|
if err != nil {
|
||||||
|
return remoteAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ports used number in each client
|
||||||
|
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum()
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteAddr, err = pxy.Run()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = ctl.pxyManager.Add(pxyMsg.ProxyName, pxy)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl.mu.Lock()
|
||||||
|
ctl.proxies[pxy.GetName()] = pxy
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.GlbServerCfg.MaxPortsPerClient > 0 {
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
|
}
|
||||||
|
pxy.Close()
|
||||||
|
ctl.pxyManager.Del(pxy.GetName())
|
||||||
|
delete(ctl.proxies, closeMsg.ProxyName)
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{
|
||||||
|
Name: pxy.GetName(),
|
||||||
|
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
49
server/controller/resource.go
Normal file
49
server/controller/resource.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2019 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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/nathole"
|
||||||
|
"github.com/fatedier/frp/server/group"
|
||||||
|
"github.com/fatedier/frp/server/ports"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
// All resource managers and controllers
|
||||||
|
type ResourceController struct {
|
||||||
|
// Manage all visitor listeners
|
||||||
|
VisitorManager *VisitorManager
|
||||||
|
|
||||||
|
// Tcp Group Controller
|
||||||
|
TcpGroupCtl *group.TcpGroupCtl
|
||||||
|
|
||||||
|
// HTTP Group Controller
|
||||||
|
HTTPGroupCtl *group.HTTPGroupController
|
||||||
|
|
||||||
|
// Manage all tcp ports
|
||||||
|
TcpPortManager *ports.PortManager
|
||||||
|
|
||||||
|
// Manage all udp ports
|
||||||
|
UdpPortManager *ports.PortManager
|
||||||
|
|
||||||
|
// For http proxies, forwarding http requests
|
||||||
|
HttpReverseProxy *vhost.HttpReverseProxy
|
||||||
|
|
||||||
|
// For https proxies, route requests to different clients by hostname and other information
|
||||||
|
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||||
|
|
||||||
|
// Controller for nat hole connections
|
||||||
|
NatHoleController *nathole.NatHoleController
|
||||||
|
}
|
||||||
95
server/controller/visitor.go
Normal file
95
server/controller/visitor.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2019 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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager for visitor listeners.
|
||||||
|
type VisitorManager struct {
|
||||||
|
visitorListeners map[string]*frpNet.CustomListener
|
||||||
|
skMap map[string]string
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorManager() *VisitorManager {
|
||||||
|
return &VisitorManager{
|
||||||
|
visitorListeners: make(map[string]*frpNet.CustomListener),
|
||||||
|
skMap: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := vm.visitorListeners[name]; ok {
|
||||||
|
err = fmt.Errorf("custom listener for [%s] is repeated", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l = frpNet.NewCustomListener()
|
||||||
|
vm.visitorListeners[name] = l
|
||||||
|
vm.skMap[name] = sk
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
|
||||||
|
useEncryption bool, useCompression bool) (err error) {
|
||||||
|
|
||||||
|
vm.mu.RLock()
|
||||||
|
defer vm.mu.RUnlock()
|
||||||
|
|
||||||
|
if l, ok := vm.visitorListeners[name]; ok {
|
||||||
|
var sk string
|
||||||
|
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
|
||||||
|
err = fmt.Errorf("visitor connection of [%s] auth failed", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
if useEncryption {
|
||||||
|
if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil {
|
||||||
|
err = fmt.Errorf("create encryption connection failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if useCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn))
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) CloseListener(name string) {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
delete(vm.visitorListeners, name)
|
||||||
|
delete(vm.skMap, name)
|
||||||
|
}
|
||||||
73
server/dashboard.go
Normal file
73
server/dashboard.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/assets"
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpServerReadTimeout = 10 * time.Second
|
||||||
|
httpServerWriteTimeout = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
|
||||||
|
// url router
|
||||||
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
|
||||||
|
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
|
||||||
|
|
||||||
|
// api, see dashboard_api.go
|
||||||
|
router.HandleFunc("/api/serverinfo", svr.ApiServerInfo).Methods("GET")
|
||||||
|
router.HandleFunc("/api/proxy/{type}", svr.ApiProxyByType).Methods("GET")
|
||||||
|
router.HandleFunc("/api/proxy/{type}/{name}", svr.ApiProxyByTypeAndName).Methods("GET")
|
||||||
|
router.HandleFunc("/api/traffic/{name}", svr.ApiProxyTraffic).Methods("GET")
|
||||||
|
|
||||||
|
// view
|
||||||
|
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
|
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
|
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 server.Serve(ln)
|
||||||
|
return
|
||||||
|
}
|
||||||
324
server/dashboard_api.go
Normal file
324
server/dashboard_api.go
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
// 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeneralResponse struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerInfoResp struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
|
VhostHttpPort int `json:"vhost_http_port"`
|
||||||
|
VhostHttpsPort int `json:"vhost_https_port"`
|
||||||
|
KcpBindPort int `json:"kcp_bind_port"`
|
||||||
|
SubdomainHost string `json:"subdomain_host"`
|
||||||
|
MaxPoolCount int64 `json:"max_pool_count"`
|
||||||
|
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||||
|
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||||
|
|
||||||
|
TotalTrafficIn int64 `json:"total_traffic_in"`
|
||||||
|
TotalTrafficOut int64 `json:"total_traffic_out"`
|
||||||
|
CurConns int64 `json:"cur_conns"`
|
||||||
|
ClientCounts int64 `json:"client_counts"`
|
||||||
|
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// api/serverinfo
|
||||||
|
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||||
|
serverStats := svr.statsCollector.GetServer()
|
||||||
|
svrResp := ServerInfoResp{
|
||||||
|
Version: version.Full(),
|
||||||
|
BindPort: cfg.BindPort,
|
||||||
|
BindUdpPort: cfg.BindUdpPort,
|
||||||
|
VhostHttpPort: cfg.VhostHttpPort,
|
||||||
|
VhostHttpsPort: cfg.VhostHttpsPort,
|
||||||
|
KcpBindPort: cfg.KcpBindPort,
|
||||||
|
SubdomainHost: cfg.SubDomainHost,
|
||||||
|
MaxPoolCount: cfg.MaxPoolCount,
|
||||||
|
MaxPortsPerClient: cfg.MaxPortsPerClient,
|
||||||
|
HeartBeatTimeout: cfg.HeartBeatTimeout,
|
||||||
|
|
||||||
|
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||||
|
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||||
|
CurConns: serverStats.CurConns,
|
||||||
|
ClientCounts: serverStats.ClientCounts,
|
||||||
|
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&svrResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseOutConf struct {
|
||||||
|
config.BaseProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
type TcpOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UdpOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
config.DomainConf
|
||||||
|
Locations []string `json:"locations"`
|
||||||
|
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpsOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
config.DomainConf
|
||||||
|
}
|
||||||
|
|
||||||
|
type StcpOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpOutConf struct {
|
||||||
|
BaseOutConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfByType(proxyType string) interface{} {
|
||||||
|
switch proxyType {
|
||||||
|
case consts.TcpProxy:
|
||||||
|
return &TcpOutConf{}
|
||||||
|
case consts.UdpProxy:
|
||||||
|
return &UdpOutConf{}
|
||||||
|
case consts.HttpProxy:
|
||||||
|
return &HttpOutConf{}
|
||||||
|
case consts.HttpsProxy:
|
||||||
|
return &HttpsOutConf{}
|
||||||
|
case consts.StcpProxy:
|
||||||
|
return &StcpOutConf{}
|
||||||
|
case consts.XtcpProxy:
|
||||||
|
return &XtcpOutConf{}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get proxy info.
|
||||||
|
type ProxyStatsInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Conf interface{} `json:"conf"`
|
||||||
|
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||||
|
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||||
|
CurConns int64 `json:"cur_conns"`
|
||||||
|
LastStartTime string `json:"last_start_time"`
|
||||||
|
LastCloseTime string `json:"last_close_time"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetProxyInfoResp struct {
|
||||||
|
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// api/proxy/:type
|
||||||
|
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
params := mux.Vars(r)
|
||||||
|
proxyType := params["type"]
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&proxyInfoResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||||
|
proxyStats := svr.statsCollector.GetProxiesByType(proxyType)
|
||||||
|
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||||
|
for _, ps := range proxyStats {
|
||||||
|
proxyInfo := &ProxyStatsInfo{}
|
||||||
|
if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
|
||||||
|
content, err := json.Marshal(pxy.GetConf())
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxyInfo.Conf = getConfByType(ps.Type)
|
||||||
|
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||||
|
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxyInfo.Status = consts.Online
|
||||||
|
} else {
|
||||||
|
proxyInfo.Status = consts.Offline
|
||||||
|
}
|
||||||
|
proxyInfo.Name = ps.Name
|
||||||
|
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||||
|
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||||
|
proxyInfo.CurConns = ps.CurConns
|
||||||
|
proxyInfo.LastStartTime = ps.LastStartTime
|
||||||
|
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||||
|
proxyInfos = append(proxyInfos, proxyInfo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get proxy info by name.
|
||||||
|
type GetProxyStatsResp struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Conf interface{} `json:"conf"`
|
||||||
|
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||||
|
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||||
|
CurConns int64 `json:"cur_conns"`
|
||||||
|
LastStartTime string `json:"last_start_time"`
|
||||||
|
LastCloseTime string `json:"last_close_time"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// api/proxy/:type/:name
|
||||||
|
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
params := mux.Vars(r)
|
||||||
|
proxyType := params["type"]
|
||||||
|
name := params["name"]
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
|
proxyStatsResp := GetProxyStatsResp{}
|
||||||
|
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||||
|
if res.Code != 200 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&proxyStatsResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||||
|
proxyInfo.Name = proxyName
|
||||||
|
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||||
|
if ps == nil {
|
||||||
|
code = 404
|
||||||
|
msg = "no proxy info found"
|
||||||
|
} else {
|
||||||
|
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||||
|
content, err := json.Marshal(pxy.GetConf())
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
|
code = 400
|
||||||
|
msg = "parse conf error"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxyInfo.Conf = getConfByType(ps.Type)
|
||||||
|
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||||
|
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
|
code = 400
|
||||||
|
msg = "parse conf error"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxyInfo.Status = consts.Online
|
||||||
|
} else {
|
||||||
|
proxyInfo.Status = consts.Offline
|
||||||
|
}
|
||||||
|
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||||
|
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||||
|
proxyInfo.CurConns = ps.CurConns
|
||||||
|
proxyInfo.LastStartTime = ps.LastStartTime
|
||||||
|
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||||
|
code = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// api/traffic/:name
|
||||||
|
type GetProxyTrafficResp struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
TrafficIn []int64 `json:"traffic_in"`
|
||||||
|
TrafficOut []int64 `json:"traffic_out"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
params := mux.Vars(r)
|
||||||
|
name := params["name"]
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
|
trafficResp := GetProxyTrafficResp{}
|
||||||
|
trafficResp.Name = name
|
||||||
|
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
||||||
|
|
||||||
|
if proxyTrafficInfo == nil {
|
||||||
|
res.Code = 404
|
||||||
|
res.Msg = "no proxy info found"
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||||
|
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&trafficResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
|
}
|
||||||
27
server/group/group.go
Normal file
27
server/group/group.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrGroupAuthFailed = errors.New("group auth failed")
|
||||||
|
ErrGroupParamsInvalid = errors.New("group params invalid")
|
||||||
|
ErrListenerClosed = errors.New("group listener closed")
|
||||||
|
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
||||||
|
ErrProxyRepeated = errors.New("group proxy repeated")
|
||||||
|
)
|
||||||
157
server/group/http.go
Normal file
157
server/group/http.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPGroupController struct {
|
||||||
|
groups map[string]*HTTPGroup
|
||||||
|
|
||||||
|
vhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupController {
|
||||||
|
return &HTTPGroupController{
|
||||||
|
groups: make(map[string]*HTTPGroup),
|
||||||
|
vhostRouter: vhostRouter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
indexKey := httpGroupIndex(group, routeConfig.Domain, routeConfig.Location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
g = NewHTTPGroup(ctl)
|
||||||
|
ctl.groups[indexKey] = g
|
||||||
|
}
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
return g.Register(proxyName, group, groupKey, routeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) UnRegister(proxyName, group, domain, location string) {
|
||||||
|
indexKey := httpGroupIndex(group, domain, location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty := g.UnRegister(proxyName)
|
||||||
|
if isEmpty {
|
||||||
|
delete(ctl.groups, indexKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPGroup struct {
|
||||||
|
group string
|
||||||
|
groupKey string
|
||||||
|
domain string
|
||||||
|
location string
|
||||||
|
|
||||||
|
createFuncs map[string]vhost.CreateConnFunc
|
||||||
|
pxyNames []string
|
||||||
|
index uint64
|
||||||
|
ctl *HTTPGroupController
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {
|
||||||
|
return &HTTPGroup{
|
||||||
|
createFuncs: make(map[string]vhost.CreateConnFunc),
|
||||||
|
pxyNames: make([]string, 0),
|
||||||
|
ctl: ctl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
// the first proxy in this group
|
||||||
|
tmp := routeConfig // copy object
|
||||||
|
tmp.CreateConnFn = g.createConn
|
||||||
|
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.group = group
|
||||||
|
g.groupKey = groupKey
|
||||||
|
g.domain = routeConfig.Domain
|
||||||
|
g.location = routeConfig.Location
|
||||||
|
} else {
|
||||||
|
if g.group != group || g.domain != routeConfig.Domain || g.location != routeConfig.Location {
|
||||||
|
err = ErrGroupParamsInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if g.groupKey != groupKey {
|
||||||
|
err = ErrGroupAuthFailed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := g.createFuncs[proxyName]; ok {
|
||||||
|
err = ErrProxyRepeated
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.createFuncs[proxyName] = routeConfig.CreateConnFn
|
||||||
|
g.pxyNames = append(g.pxyNames, proxyName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
delete(g.createFuncs, proxyName)
|
||||||
|
for i, name := range g.pxyNames {
|
||||||
|
if name == proxyName {
|
||||||
|
g.pxyNames = append(g.pxyNames[:i], g.pxyNames[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
isEmpty = true
|
||||||
|
g.ctl.vhostRouter.Del(g.domain, g.location)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
|
||||||
|
var f vhost.CreateConnFunc
|
||||||
|
newIndex := atomic.AddUint64(&g.index, 1)
|
||||||
|
|
||||||
|
g.mu.RLock()
|
||||||
|
group := g.group
|
||||||
|
domain := g.domain
|
||||||
|
location := g.location
|
||||||
|
if len(g.pxyNames) > 0 {
|
||||||
|
name := g.pxyNames[int(newIndex)%len(g.pxyNames)]
|
||||||
|
f, _ = g.createFuncs[name]
|
||||||
|
}
|
||||||
|
g.mu.RUnlock()
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
return nil, fmt.Errorf("no CreateConnFunc for http group [%s], domain [%s], location [%s]", group, domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpGroupIndex(group, domain, location string) string {
|
||||||
|
return fmt.Sprintf("%s_%s_%s", group, domain, location)
|
||||||
|
}
|
||||||
224
server/group/tcp.go
Normal file
224
server/group/tcp.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// 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 group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/server/ports"
|
||||||
|
|
||||||
|
gerr "github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TcpGroupCtl manage all TcpGroups
|
||||||
|
type TcpGroupCtl struct {
|
||||||
|
groups map[string]*TcpGroup
|
||||||
|
|
||||||
|
// portManager is used to manage port
|
||||||
|
portManager *ports.PortManager
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTcpGroupCtl return a new TcpGroupCtl
|
||||||
|
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||||
|
return &TcpGroupCtl{
|
||||||
|
groups: make(map[string]*TcpGroup),
|
||||||
|
portManager: portManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen is the wrapper for TcpGroup's Listen
|
||||||
|
// If there are no group, we will create one here
|
||||||
|
func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
|
||||||
|
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||||
|
|
||||||
|
tgc.mu.Lock()
|
||||||
|
tcpGroup, ok := tgc.groups[group]
|
||||||
|
if !ok {
|
||||||
|
tcpGroup = NewTcpGroup(tgc)
|
||||||
|
tgc.groups[group] = tcpGroup
|
||||||
|
}
|
||||||
|
tgc.mu.Unlock()
|
||||||
|
|
||||||
|
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveGroup remove TcpGroup from controller
|
||||||
|
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||||
|
tgc.mu.Lock()
|
||||||
|
defer tgc.mu.Unlock()
|
||||||
|
delete(tgc.groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TcpGroup route connections to different proxies
|
||||||
|
type TcpGroup struct {
|
||||||
|
group string
|
||||||
|
groupKey string
|
||||||
|
addr string
|
||||||
|
port int
|
||||||
|
realPort int
|
||||||
|
|
||||||
|
acceptCh chan net.Conn
|
||||||
|
index uint64
|
||||||
|
tcpLn net.Listener
|
||||||
|
lns []*TcpGroupListener
|
||||||
|
ctl *TcpGroupCtl
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTcpGroup return a new TcpGroup
|
||||||
|
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||||
|
return &TcpGroup{
|
||||||
|
lns: make([]*TcpGroupListener, 0),
|
||||||
|
ctl: ctl,
|
||||||
|
acceptCh: make(chan net.Conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen will return a new TcpGroupListener
|
||||||
|
// if TcpGroup already has a listener, just add a new TcpGroupListener to the queues
|
||||||
|
// otherwise, listen on the real address
|
||||||
|
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
||||||
|
tg.mu.Lock()
|
||||||
|
defer tg.mu.Unlock()
|
||||||
|
if len(tg.lns) == 0 {
|
||||||
|
// the first listener, listen on the real address
|
||||||
|
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ln = newTcpGroupListener(group, tg, tcpLn.Addr())
|
||||||
|
|
||||||
|
tg.group = group
|
||||||
|
tg.groupKey = groupKey
|
||||||
|
tg.addr = addr
|
||||||
|
tg.port = port
|
||||||
|
tg.realPort = realPort
|
||||||
|
tg.tcpLn = tcpLn
|
||||||
|
tg.lns = append(tg.lns, ln)
|
||||||
|
if tg.acceptCh == nil {
|
||||||
|
tg.acceptCh = make(chan net.Conn)
|
||||||
|
}
|
||||||
|
go tg.worker()
|
||||||
|
} else {
|
||||||
|
// address and port in the same group must be equal
|
||||||
|
if tg.group != group || tg.addr != addr {
|
||||||
|
err = ErrGroupParamsInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tg.port != port {
|
||||||
|
err = ErrGroupDifferentPort
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tg.groupKey != groupKey {
|
||||||
|
err = ErrGroupAuthFailed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ln = newTcpGroupListener(group, tg, tg.lns[0].Addr())
|
||||||
|
realPort = tg.realPort
|
||||||
|
tg.lns = append(tg.lns, ln)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// worker is called when the real tcp listener has been created
|
||||||
|
func (tg *TcpGroup) worker() {
|
||||||
|
for {
|
||||||
|
c, err := tg.tcpLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = gerr.PanicToError(func() {
|
||||||
|
tg.acceptCh <- c
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tg *TcpGroup) Accept() <-chan net.Conn {
|
||||||
|
return tg.acceptCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseListener remove the TcpGroupListener from the TcpGroup
|
||||||
|
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||||
|
tg.mu.Lock()
|
||||||
|
defer tg.mu.Unlock()
|
||||||
|
for i, tmpLn := range tg.lns {
|
||||||
|
if tmpLn == ln {
|
||||||
|
tg.lns = append(tg.lns[:i], tg.lns[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(tg.lns) == 0 {
|
||||||
|
close(tg.acceptCh)
|
||||||
|
tg.tcpLn.Close()
|
||||||
|
tg.ctl.portManager.Release(tg.realPort)
|
||||||
|
tg.ctl.RemoveGroup(tg.group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TcpGroupListener
|
||||||
|
type TcpGroupListener struct {
|
||||||
|
groupName string
|
||||||
|
group *TcpGroup
|
||||||
|
|
||||||
|
addr net.Addr
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||||
|
return &TcpGroupListener{
|
||||||
|
groupName: name,
|
||||||
|
group: group,
|
||||||
|
addr: addr,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept will accept connections from TcpGroup
|
||||||
|
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||||
|
var ok bool
|
||||||
|
select {
|
||||||
|
case <-ln.closeCh:
|
||||||
|
return nil, ErrListenerClosed
|
||||||
|
case c, ok = <-ln.group.Accept():
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrListenerClosed
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||||
|
return ln.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close the listener
|
||||||
|
func (ln *TcpGroupListener) Close() (err error) {
|
||||||
|
close(ln.closeCh)
|
||||||
|
|
||||||
|
// remove self from TcpGroup
|
||||||
|
ln.group.CloseListener(ln)
|
||||||
|
return
|
||||||
|
}
|
||||||
180
server/ports/ports.go
Normal file
180
server/ports/ports.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MinPort = 1
|
||||||
|
MaxPort = 65535
|
||||||
|
MaxPortReservedDuration = time.Duration(24) * time.Hour
|
||||||
|
CleanReservedPortsInterval = time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPortAlreadyUsed = errors.New("port already used")
|
||||||
|
ErrPortNotAllowed = errors.New("port not allowed")
|
||||||
|
ErrPortUnAvailable = errors.New("port unavailable")
|
||||||
|
ErrNoAvailablePort = errors.New("no available port")
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortCtx struct {
|
||||||
|
ProxyName string
|
||||||
|
Port int
|
||||||
|
Closed bool
|
||||||
|
UpdateTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortManager struct {
|
||||||
|
reservedPorts map[string]*PortCtx
|
||||||
|
usedPorts map[int]*PortCtx
|
||||||
|
freePorts map[int]struct{}
|
||||||
|
|
||||||
|
bindAddr string
|
||||||
|
netType string
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager {
|
||||||
|
pm := &PortManager{
|
||||||
|
reservedPorts: make(map[string]*PortCtx),
|
||||||
|
usedPorts: make(map[int]*PortCtx),
|
||||||
|
freePorts: make(map[int]struct{}),
|
||||||
|
bindAddr: bindAddr,
|
||||||
|
netType: netType,
|
||||||
|
}
|
||||||
|
if len(allowPorts) > 0 {
|
||||||
|
for port, _ := range allowPorts {
|
||||||
|
pm.freePorts[port] = struct{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := MinPort; i <= MaxPort; i++ {
|
||||||
|
pm.freePorts[i] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go pm.cleanReservedPortsWorker()
|
||||||
|
return pm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) {
|
||||||
|
portCtx := &PortCtx{
|
||||||
|
ProxyName: name,
|
||||||
|
Closed: false,
|
||||||
|
UpdateTime: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
portCtx.Port = realPort
|
||||||
|
}
|
||||||
|
pm.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// check reserved ports first
|
||||||
|
if port == 0 {
|
||||||
|
if ctx, ok := pm.reservedPorts[name]; ok {
|
||||||
|
if pm.isPortAvailable(ctx.Port) {
|
||||||
|
realPort = ctx.Port
|
||||||
|
pm.usedPorts[realPort] = portCtx
|
||||||
|
pm.reservedPorts[name] = portCtx
|
||||||
|
delete(pm.freePorts, realPort)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
// get random port
|
||||||
|
count := 0
|
||||||
|
maxTryTimes := 5
|
||||||
|
for k, _ := range pm.freePorts {
|
||||||
|
count++
|
||||||
|
if count > maxTryTimes {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if pm.isPortAvailable(k) {
|
||||||
|
realPort = k
|
||||||
|
pm.usedPorts[realPort] = portCtx
|
||||||
|
pm.reservedPorts[name] = portCtx
|
||||||
|
delete(pm.freePorts, realPort)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if realPort == 0 {
|
||||||
|
err = ErrNoAvailablePort
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// specified port
|
||||||
|
if _, ok = pm.freePorts[port]; ok {
|
||||||
|
if pm.isPortAvailable(port) {
|
||||||
|
realPort = port
|
||||||
|
pm.usedPorts[realPort] = portCtx
|
||||||
|
pm.reservedPorts[name] = portCtx
|
||||||
|
delete(pm.freePorts, realPort)
|
||||||
|
} else {
|
||||||
|
err = ErrPortUnAvailable
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok = pm.usedPorts[port]; ok {
|
||||||
|
err = ErrPortAlreadyUsed
|
||||||
|
} else {
|
||||||
|
err = ErrPortNotAllowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) isPortAvailable(port int) bool {
|
||||||
|
if pm.netType == "udp" {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l.Close()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) Release(port int) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
if ctx, ok := pm.usedPorts[port]; ok {
|
||||||
|
pm.freePorts[port] = struct{}{}
|
||||||
|
delete(pm.usedPorts, port)
|
||||||
|
ctx.Closed = true
|
||||||
|
ctx.UpdateTime = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release reserved port if it isn't used in last 24 hours.
|
||||||
|
func (pm *PortManager) cleanReservedPortsWorker() {
|
||||||
|
for {
|
||||||
|
time.Sleep(CleanReservedPortsInterval)
|
||||||
|
pm.mu.Lock()
|
||||||
|
for name, ctx := range pm.reservedPorts {
|
||||||
|
if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration {
|
||||||
|
delete(pm.reservedPorts, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pm.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
183
server/proxy/http.go
Normal file
183
server/proxy/http.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/server/stats"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.HttpProxyConf
|
||||||
|
|
||||||
|
closeFuncs []func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
routeConfig := vhost.VhostRouteConfig{
|
||||||
|
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||||
|
Headers: pxy.cfg.Headers,
|
||||||
|
Username: pxy.cfg.HttpUser,
|
||||||
|
Password: pxy.cfg.HttpPwd,
|
||||||
|
CreateConnFn: pxy.GetRealConn,
|
||||||
|
}
|
||||||
|
|
||||||
|
locations := pxy.cfg.Locations
|
||||||
|
if len(locations) == 0 {
|
||||||
|
locations = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
addrs := make([]string, 0)
|
||||||
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
routeConfig.Domain = domain
|
||||||
|
for _, location := range locations {
|
||||||
|
routeConfig.Location = location
|
||||||
|
tmpDomain := routeConfig.Domain
|
||||||
|
tmpLocation := routeConfig.Location
|
||||||
|
|
||||||
|
// handle group
|
||||||
|
if pxy.cfg.Group != "" {
|
||||||
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// no group
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpPort)))
|
||||||
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pxy.cfg.SubDomain != "" {
|
||||||
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||||
|
for _, location := range locations {
|
||||||
|
routeConfig.Location = location
|
||||||
|
tmpDomain := routeConfig.Domain
|
||||||
|
tmpLocation := routeConfig.Location
|
||||||
|
|
||||||
|
// handle group
|
||||||
|
if pxy.cfg.Group != "" {
|
||||||
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
||||||
|
|
||||||
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
||||||
|
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||||
|
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = tmpConn
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||||
|
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||||
|
pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
||||||
|
name := pxy.GetName()
|
||||||
|
pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name})
|
||||||
|
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||||
|
ProxyName: name,
|
||||||
|
TrafficBytes: totalWrite,
|
||||||
|
})
|
||||||
|
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||||
|
ProxyName: name,
|
||||||
|
TrafficBytes: totalRead,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
for _, closeFn := range pxy.closeFuncs {
|
||||||
|
closeFn()
|
||||||
|
}
|
||||||
|
}
|
||||||
81
server/proxy/https.go
Normal file
81
server/proxy/https.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpsProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.HttpsProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||||
|
routeConfig := &vhost.VhostRouteConfig{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
addrs := make([]string, 0)
|
||||||
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
routeConfig.Domain = domain
|
||||||
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.AddLogPrefix(pxy.name)
|
||||||
|
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
if pxy.cfg.SubDomain != "" {
|
||||||
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||||
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.AddLogPrefix(pxy.name)
|
||||||
|
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
|
||||||
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpsProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
}
|
||||||
275
server/proxy/proxy.go
Normal file
275
server/proxy/proxy.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/server/controller"
|
||||||
|
"github.com/fatedier/frp/server/stats"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetWorkConnFn func() (frpNet.Conn, error)
|
||||||
|
|
||||||
|
type Proxy interface {
|
||||||
|
Run() (remoteAddr string, err error)
|
||||||
|
GetName() string
|
||||||
|
GetConf() config.ProxyConf
|
||||||
|
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
||||||
|
GetUsedPortsNum() int
|
||||||
|
Close()
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseProxy struct {
|
||||||
|
name string
|
||||||
|
rc *controller.ResourceController
|
||||||
|
statsCollector stats.Collector
|
||||||
|
listeners []frpNet.Listener
|
||||||
|
usedPortsNum int
|
||||||
|
poolCount int
|
||||||
|
getWorkConnFn GetWorkConnFn
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) GetName() string {
|
||||||
|
return pxy.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||||
|
return pxy.usedPortsNum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) Close() {
|
||||||
|
pxy.Info("proxy closing")
|
||||||
|
for _, l := range pxy.listeners {
|
||||||
|
l.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorkConnFromPool try to get a new work connections from pool
|
||||||
|
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
||||||
|
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||||
|
// try all connections from the pool
|
||||||
|
for i := 0; i < pxy.poolCount+1; i++ {
|
||||||
|
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||||
|
pxy.Warn("failed to get work connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
|
workConn.AddLogPrefix(pxy.GetName())
|
||||||
|
|
||||||
|
var (
|
||||||
|
srcAddr string
|
||||||
|
dstAddr string
|
||||||
|
srcPortStr string
|
||||||
|
dstPortStr string
|
||||||
|
srcPort int
|
||||||
|
dstPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
if src != nil {
|
||||||
|
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||||
|
srcPort, _ = strconv.Atoi(srcPortStr)
|
||||||
|
}
|
||||||
|
if dst != nil {
|
||||||
|
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||||
|
dstPort, _ = strconv.Atoi(dstPortStr)
|
||||||
|
}
|
||||||
|
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
SrcAddr: srcAddr,
|
||||||
|
SrcPort: uint16(srcPort),
|
||||||
|
DstAddr: dstAddr,
|
||||||
|
DstPort: uint16(dstPort),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||||
|
workConn.Close()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("try to get work connection failed in the end")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// startListenHandler start a goroutine handler for each listener.
|
||||||
|
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
||||||
|
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||||
|
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
|
||||||
|
for _, listener := range pxy.listeners {
|
||||||
|
go func(l frpNet.Listener) {
|
||||||
|
for {
|
||||||
|
// block
|
||||||
|
// if listener is closed, err returned
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
pxy.Info("listener is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
||||||
|
go handler(p, c, pxy.statsCollector)
|
||||||
|
}
|
||||||
|
}(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
|
||||||
|
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||||
|
|
||||||
|
basePxy := BaseProxy{
|
||||||
|
name: pxyConf.GetBaseInfo().ProxyName,
|
||||||
|
rc: rc,
|
||||||
|
statsCollector: statsCollector,
|
||||||
|
listeners: make([]frpNet.Listener, 0),
|
||||||
|
poolCount: poolCount,
|
||||||
|
getWorkConnFn: getWorkConnFn,
|
||||||
|
Logger: log.NewPrefixLogger(runId),
|
||||||
|
}
|
||||||
|
switch cfg := pxyConf.(type) {
|
||||||
|
case *config.TcpProxyConf:
|
||||||
|
basePxy.usedPortsNum = 1
|
||||||
|
pxy = &TcpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.HttpProxyConf:
|
||||||
|
pxy = &HttpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.HttpsProxyConf:
|
||||||
|
pxy = &HttpsProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.UdpProxyConf:
|
||||||
|
basePxy.usedPortsNum = 1
|
||||||
|
pxy = &UdpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
pxy = &StcpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return pxy, fmt.Errorf("proxy type not support")
|
||||||
|
}
|
||||||
|
pxy.AddLogPrefix(pxy.GetName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleUserTcpConnection is used for incoming tcp user connections.
|
||||||
|
// It can be used for tcp, http, https type.
|
||||||
|
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
// try all connections from the pool
|
||||||
|
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer workConn.Close()
|
||||||
|
|
||||||
|
var local io.ReadWriteCloser = workConn
|
||||||
|
cfg := pxy.GetConf().GetBaseInfo()
|
||||||
|
if cfg.UseEncryption {
|
||||||
|
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.UseCompression {
|
||||||
|
local = frpIo.WithCompression(local)
|
||||||
|
}
|
||||||
|
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
||||||
|
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
|
||||||
|
inCount, outCount := frpIo.Join(local, userConn)
|
||||||
|
statsCollector.Mark(stats.TypeCloseConnection, &stats.CloseConnectionPayload{ProxyName: pxy.GetName()})
|
||||||
|
statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
TrafficBytes: inCount,
|
||||||
|
})
|
||||||
|
statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
TrafficBytes: outCount,
|
||||||
|
})
|
||||||
|
pxy.Debug("join connections closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyManager struct {
|
||||||
|
// proxies indexed by proxy name
|
||||||
|
pxys map[string]Proxy
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyManager() *ProxyManager {
|
||||||
|
return &ProxyManager{
|
||||||
|
pxys: make(map[string]Proxy),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
if _, ok := pm.pxys[name]; ok {
|
||||||
|
return fmt.Errorf("proxy name [%s] is already in use", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pm.pxys[name] = pxy
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) Del(name string) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
delete(pm.pxys, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
|
||||||
|
pm.mu.RLock()
|
||||||
|
defer pm.mu.RUnlock()
|
||||||
|
pxy, ok = pm.pxys[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
47
server/proxy/stcp.go
Normal file
47
server/proxy/stcp.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StcpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener.AddLogPrefix(pxy.name)
|
||||||
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
|
pxy.Info("stcp proxy custom listen success")
|
||||||
|
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.rc.VisitorManager.CloseListener(pxy.GetName())
|
||||||
|
}
|
||||||
84
server/proxy/tcp.go
Normal file
84
server/proxy/tcp.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TcpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.TcpProxyConf
|
||||||
|
|
||||||
|
realPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
if pxy.cfg.Group != "" {
|
||||||
|
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
l.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
pxy.realPort = realPort
|
||||||
|
listener := frpNet.WrapLogListener(l)
|
||||||
|
listener.AddLogPrefix(pxy.name)
|
||||||
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
|
pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
||||||
|
} else {
|
||||||
|
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener.AddLogPrefix(pxy.name)
|
||||||
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
|
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *TcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
if pxy.cfg.Group == "" {
|
||||||
|
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
225
server/proxy/udp.go
Normal file
225
server/proxy/udp.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
// Copyright 2019 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"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/g"
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
|
"github.com/fatedier/frp/server/stats"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UdpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.UdpProxyConf
|
||||||
|
|
||||||
|
realPort int
|
||||||
|
|
||||||
|
// udpConn is the listener of udp packages
|
||||||
|
udpConn *net.UDPConn
|
||||||
|
|
||||||
|
// there are always only one workConn at the same time
|
||||||
|
// get another one if it closed
|
||||||
|
workConn net.Conn
|
||||||
|
|
||||||
|
// sendCh is used for sending packages to workConn
|
||||||
|
sendCh chan *msg.UdpPacket
|
||||||
|
|
||||||
|
// readCh is used for reading packages from workConn
|
||||||
|
readCh chan *msg.UdpPacket
|
||||||
|
|
||||||
|
// checkCloseCh is used for watching if workConn is closed
|
||||||
|
checkCloseCh chan int
|
||||||
|
|
||||||
|
isClosed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.rc.UdpPortManager.Release(pxy.realPort)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
|
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
udpConn, errRet := net.ListenUDP("udp", addr)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
pxy.Warn("listen udp port error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||||
|
|
||||||
|
pxy.udpConn = udpConn
|
||||||
|
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
|
||||||
|
pxy.readCh = make(chan *msg.UdpPacket, 1024)
|
||||||
|
pxy.checkCloseCh = make(chan int)
|
||||||
|
|
||||||
|
// read message from workConn, if it returns any error, notify proxy to start a new workConn
|
||||||
|
workConnReaderFn := func(conn net.Conn) {
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
rawMsg msg.Message
|
||||||
|
errRet error
|
||||||
|
)
|
||||||
|
pxy.Trace("loop waiting message from udp workConn")
|
||||||
|
// client will send heartbeat in workConn for keeping alive
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
|
||||||
|
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||||
|
pxy.Warn("read from workConn for udp error: %v", errRet)
|
||||||
|
conn.Close()
|
||||||
|
// notify proxy to start a new work connection
|
||||||
|
// ignore error here, it means the proxy is closed
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
pxy.checkCloseCh <- 1
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.Ping:
|
||||||
|
pxy.Trace("udp work conn get ping message")
|
||||||
|
continue
|
||||||
|
case *msg.UdpPacket:
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
pxy.Trace("get udp message from workConn: %s", m.Content)
|
||||||
|
pxy.readCh <- m
|
||||||
|
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
TrafficBytes: int64(len(m.Content)),
|
||||||
|
})
|
||||||
|
}); errRet != nil {
|
||||||
|
conn.Close()
|
||||||
|
pxy.Info("reader goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send message to workConn
|
||||||
|
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
|
||||||
|
var errRet error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case udpMsg, ok := <-pxy.sendCh:
|
||||||
|
if !ok {
|
||||||
|
pxy.Info("sender goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
|
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||||
|
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
TrafficBytes: int64(len(udpMsg.Content)),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
pxy.Info("sender goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
for {
|
||||||
|
workConn, err := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
// check if proxy is closed
|
||||||
|
select {
|
||||||
|
case _, ok := <-pxy.checkCloseCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// close the old workConn and replac it with a new one
|
||||||
|
if pxy.workConn != nil {
|
||||||
|
pxy.workConn.Close()
|
||||||
|
}
|
||||||
|
pxy.workConn = workConn
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go workConnReaderFn(workConn)
|
||||||
|
go workConnSenderFn(workConn, ctx)
|
||||||
|
_, ok := <-pxy.checkCloseCh
|
||||||
|
cancel()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
|
||||||
|
// Client will transfor udp message to local udp service and waiting for response for a while.
|
||||||
|
// Response will be wrapped to be forwarded by work connection to server.
|
||||||
|
// Close readCh and sendCh at the end.
|
||||||
|
go func() {
|
||||||
|
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
|
||||||
|
pxy.Close()
|
||||||
|
}()
|
||||||
|
return remoteAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UdpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UdpProxy) Close() {
|
||||||
|
pxy.mu.Lock()
|
||||||
|
defer pxy.mu.Unlock()
|
||||||
|
if !pxy.isClosed {
|
||||||
|
pxy.isClosed = true
|
||||||
|
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
if pxy.workConn != nil {
|
||||||
|
pxy.workConn.Close()
|
||||||
|
}
|
||||||
|
pxy.udpConn.Close()
|
||||||
|
|
||||||
|
// all channels only closed here
|
||||||
|
close(pxy.checkCloseCh)
|
||||||
|
close(pxy.readCh)
|
||||||
|
close(pxy.sendCh)
|
||||||
|
}
|
||||||
|
pxy.rc.UdpPortManager.Release(pxy.realPort)
|
||||||
|
}
|
||||||
95
server/proxy/xtcp.go
Normal file
95
server/proxy/xtcp.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XtcpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
if pxy.rc.NatHoleController == nil {
|
||||||
|
pxy.Error("udp port for xtcp is not specified.")
|
||||||
|
err = fmt.Errorf("xtcp is not supported in frps")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
break
|
||||||
|
case sidRequest := <-sidCh:
|
||||||
|
sr := sidRequest
|
||||||
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
|
if errRet != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := &msg.NatHoleSid{
|
||||||
|
Sid: sr.Sid,
|
||||||
|
}
|
||||||
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
raw, errRet := msg.ReadMsg(workConn)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||||
|
pxy.Warn("read nat hole client ok package format error")
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sr.NotifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
close(pxy.closeCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user