Compare commits

..

665 Commits

Author SHA1 Message Date
fatedier
6451583e60 Merge pull request #1349 from fatedier/dev
bump version to v0.28.0
2019-08-01 14:04:55 +08:00
fatedier
30cb0a3ab0 Merge pull request #1344 from fatedier/new
support http load balancing
2019-08-01 13:59:41 +08:00
fatedier
5680a88267 fix connection leak when login_fail_exit is false, fix #1335 2019-07-31 00:50:38 +08:00
fatedier
6b089858db bump version to v0.28.0 2019-07-31 00:47:50 +08:00
fatedier
b3ed863021 support http load balancing 2019-07-31 00:41:58 +08:00
fatedier
5796c27ed5 doc: update 2019-07-31 00:41:43 +08:00
fatedier
310e8dd768 Merge pull request #1331 from muesli/typo-fixes
Fixed typos in comments
2019-07-19 18:50:29 +08:00
Christian Muehlhaeuser
0b40ac2dbc Fixed typos in comments
Just nitpicky typo fixes.
2019-07-19 12:40:14 +02:00
fatedier
f22c8e0882 Merge pull request #1323 from skyrocknroll/skyrocknroll-patch-1
Typo
2019-07-15 14:03:57 +08:00
Yuvaraj L
a388bb2c95 Typo
English Grammar Typo
2019-07-15 01:05:43 +05:30
fatedier
e611c44dea Merge pull request #1322 from fatedier/dev
bump version to v0.27.1
2019-07-14 20:00:31 +08:00
fatedier
8e36e2bb67 Merge pull request #1320 from fatedier/new
add read timeout for TLS check operation
2019-07-14 10:57:22 +08:00
fatedier
541ad8d899 update ISSUE_TEMPLATE 2019-07-12 17:59:45 +08:00
fatedier
17cc0735d1 add read timeout for TLS check operation 2019-07-12 17:11:03 +08:00
fatedier
fd336a5503 Merge pull request #1275 from Arugal/dev
replace the _
2019-06-02 21:22:29 +08:00
zhangwei
802d1c1861 replace the _ 2019-06-01 10:09:13 +08:00
fatedier
65fe0a1179 Merge pull request #1271 from jiajunhuang/resp_body_should_be_closed
resp.Body must be closed after function return
2019-06-01 00:44:03 +08:00
Jiajun Huang
2d24879fa3 fix 2019-05-31 15:56:05 +08:00
Jiajun Huang
75383a95b3 resp.Body must be closed after function return
whether it's success or fail, otherwise it will cause memory leak
ref: https://golang.org/pkg/net/http/
2019-05-30 22:32:36 +08:00
fatedier
95444ea46b Merge pull request #1216 from fatedier/dev
bump version to v0.27.0
2019-04-25 14:41:05 +08:00
fatedier
9f9c01b520 Merge pull request #1215 from fatedier/new
merge new features
2019-04-25 14:38:05 +08:00
fatedier
285d1eba0d bump version to v0.27.0 2019-04-25 12:31:20 +08:00
fatedier
0dfd3a421c frps: support custom_404_page 2019-04-25 12:29:34 +08:00
fatedier
6a1f15b25e support proxy protocol in unix_domain_socket 2019-04-25 12:01:57 +08:00
Gihan
9f47c324b7 api error fix due to status code 2019-04-25 09:54:56 +08:00
fatedier
f0df6084af Merge pull request #1206 from bgkavinga/master
api error fix due to status code
2019-04-24 12:06:13 +08:00
Gihan
879ca47590 api error fix due to status code 2019-04-21 13:29:35 +05:30
fatedier
6a7efc81c9 Merge pull request #1191 from fatedier/dev
Bump version to v0.26.0
2019-04-10 14:02:03 +08:00
fatedier
12c5c553c3 Merge pull request #1190 from fatedier/new
new features
2019-04-10 13:57:59 +08:00
fatedier
988e9b1de3 update doc 2019-04-10 13:51:05 +08:00
fatedier
db6bbc5187 frpc: new plugin https2http 2019-04-10 12:02:22 +08:00
fatedier
c67b4e7b94 vendor: add packages 2019-04-10 10:53:45 +08:00
fatedier
b7a73d3469 support proxy protocol for type http 2019-04-10 10:51:01 +08:00
fatedier
7f9d88c10a fix 2019-04-08 15:39:14 +08:00
fatedier
79237d2b94 bump version to v0.26.0 2019-03-29 19:40:25 +08:00
fatedier
9c4ec56491 support proxy protocol 2019-03-29 19:01:18 +08:00
fatedier
74a8752570 fix route conflict 2019-03-29 17:12:44 +08:00
fatedier
a8ab4c5003 Merge pull request #1160 from fatedier/dev
bump version to v0.25.3, fix #1159
2019-03-26 19:33:39 +08:00
fatedier
9cee263c91 fix panic error when reconnecting using tls 2019-03-26 19:28:24 +08:00
fatedier
c6bf6f59e6 update package.sh 2019-03-25 18:38:02 +08:00
fatedier
4b7aef2196 Merge pull request #1157 from fatedier/dev
bump version to v0.25.2
2019-03-25 18:26:33 +08:00
fatedier
f6d0046b5a bump version to v0.25.2 2019-03-25 18:22:35 +08:00
fatedier
84363266d2 Merge pull request #1156 from fatedier/new
fix health check unclosed resp body
2019-03-25 18:22:15 +08:00
fatedier
9ac8f2a047 fix health check unclosed resp body, fix #1155 2019-03-25 18:17:33 +08:00
fatedier
b2b55533b8 Merge pull request #1147 from a-wing/dev
Add systemd unit
2019-03-21 17:46:36 +08:00
a-wing
a4cfab689a Add systemd unit
Ref https://github.com/fatedier/frp/issues/1058
Ref https://aur.archlinux.org/packages/frp/

Co-authored-by: vimsucks <dev@vimsucks.com>
2019-03-21 11:38:34 +08:00
fatedier
c7df39074c Merge pull request #1140 from fatedier/kcp
update kcp-go package
2019-03-17 17:15:44 +08:00
fatedier
fdcdccb0c2 update kcp-go package 2019-03-17 17:09:54 +08:00
fatedier
e945c1667a Merge pull request #1138 from fatedier/dev
bump version to v0.25.1
2019-03-15 17:05:09 +08:00
fatedier
87a4de4370 Merge pull request #1137 from fatedier/fix
some fixes
2019-03-15 17:00:59 +08:00
fatedier
e1e2913b77 bump version to v0.25.1 2019-03-15 16:46:22 +08:00
fatedier
9be24db410 support multilevel subdomain, fix #1132 2019-03-15 16:22:41 +08:00
fatedier
6b61cb3742 fix frps --log_file useless, fix #1125 2019-03-15 15:37:17 +08:00
fatedier
90b7f2080f Merge pull request #1122 from fatedier/dev
bump version to v0.25.0
2019-03-11 17:40:39 +08:00
fatedier
d1f1c72a55 update ci 2019-03-11 17:11:26 +08:00
fatedier
1925847ef8 update doc 2019-03-11 16:24:54 +08:00
fatedier
8b216b0ca9 Merge pull request #1121 from fatedier/new
new feature
2019-03-11 16:05:18 +08:00
fatedier
dbfeea99f3 update .travis.yml, support go1.12 2019-03-11 16:02:45 +08:00
fatedier
5e64bbfa7c vendor: update package 2019-03-11 15:54:55 +08:00
fatedier
e691a40260 improve the stability of xtcp 2019-03-11 15:53:58 +08:00
fatedier
d812488767 support tls connection 2019-03-11 14:14:31 +08:00
fatedier
3c03690ab7 Merge pull request #1112 from fatedier/p2p
xtcp: wrap yamux on kcp connections, fix #1103
2019-03-05 11:27:15 +08:00
fatedier
3df27b9c04 xtcp: wrap yamux on kcp connections 2019-03-05 11:18:17 +08:00
fatedier
ba45d29b7c fix xtcp cmd 2019-03-03 23:44:44 +08:00
fatedier
3cf83f57a8 update yamux version 2019-03-03 22:29:08 +08:00
fatedier
03e4318d79 Merge pull request #1107 from likev/patch-1
Update instruction of 'Rewriting the Host Header'
2019-03-03 21:57:24 +08:00
xufanglu
178d134f46 Update instruction of 'Rewriting the Host Header'
Update instruction of 'Rewriting the Host Header' in README.md
2019-03-02 21:33:23 +08:00
fatedier
cbf9c731a0 Merge pull request #1088 from fatedier/dev
bump version to v0.24.1
2019-02-12 15:10:43 +08:00
fatedier
de4bfcc43c bump version to v0.24.1 2019-02-12 15:03:40 +08:00
fatedier
9737978f28 Merge pull request #1087 from fatedier/fix
fix PUT /api/config without token
2019-02-12 15:03:00 +08:00
fatedier
5bc7fe2cea fix PUT /api/config without token 2019-02-12 14:59:30 +08:00
fatedier
65d8fe37c5 Merge pull request #1081 from fatedier/dev
bump version to v0.24.0
2019-02-11 14:46:23 +08:00
fatedier
1723d7b651 Merge pull request #1080 from fatedier/client
frpc: support admin UI
2019-02-11 14:42:48 +08:00
fatedier
2481dfab64 fix api 2019-02-11 14:37:52 +08:00
fatedier
95a881a7d3 frps: update server dashboard_api 2019-02-11 11:42:07 +08:00
fatedier
fe403ab328 frpc: update admin_api 2019-02-11 11:26:06 +08:00
fatedier
66555dbb00 frpc admin: not allow empty PUT /api/config body 2019-02-02 11:46:53 +08:00
fatedier
7f9ea48405 bump version to v0.24.0 2019-02-01 19:28:38 +08:00
fatedier
96d7e2da6f add admin UI for frpc 2019-02-01 19:28:05 +08:00
fatedier
d879b8208b frpc: add api PUT api/config 2019-01-31 18:35:44 +08:00
fatedier
3585e456d4 frpc: add api GET api/config 2019-01-31 17:17:34 +08:00
fatedier
1de8c3fc87 Merge pull request #1069 from fatedier/vet
go vet & golint
2019-01-31 16:59:03 +08:00
fatedier
bbab3fe9ca go lint 2019-01-31 16:54:46 +08:00
fatedier
48990da22e go vet 2019-01-31 16:49:23 +08:00
fatedier
5543fc2a9a Merge pull request #1068 from fatedier/dev
bump version to v0.23.3
2019-01-30 11:38:39 +08:00
fatedier
c41de6fd28 bump version 2019-01-30 11:22:25 +08:00
fatedier
8c8fd9790e Merge pull request #1067 from fatedier/fix
frpc: reload proxy not saved after reconnecting
2019-01-30 11:22:41 +08:00
fatedier
5a7ef3be74 frpc: reload proxy not saved after reconnecting 2019-01-30 11:12:28 +08:00
fatedier
d9b5e0bde0 Merge pull request #1061 from fatedier/dev
bump version to v0.23.2
2019-01-26 22:11:57 +08:00
fatedier
05ca72dbf0 bump version 2019-01-26 22:05:58 +08:00
fatedier
ef6f8bbf6c Merge pull request #1060 from fatedier/new
fix control delete error
2019-01-26 22:05:38 +08:00
fatedier
70ac7d3d11 fix control delete error 2019-01-26 21:36:24 +08:00
fatedier
385c4d3dd5 frpc/cmd: update protocol description 2019-01-26 12:52:12 +08:00
fatedier
5e1983f7ed change from dep to go mod 2019-01-26 12:39:03 +08:00
fatedier
516cdbddb0 support go mod 2019-01-16 20:48:47 +08:00
fatedier
3954ceb93b Merge pull request #1049 from fatedier/dev
bump version to v0.23.1
2019-01-16 14:40:29 +08:00
fatedier
2061ef11c8 bump version to v0.23.1 2019-01-16 14:35:22 +08:00
fatedier
71cbe5decc Merge pull request #1048 from 442hz/frpc-fixup-sub-command-status-and-reload-ini-parse-problem
frpc: fixup ini config parse problem in sub command `status` and `rel…
2019-01-16 14:34:20 +08:00
荒野無燈
a2ccb6c190 frpc: fixup ini config parse problem in sub command status and reload. 2019-01-16 13:12:25 +08:00
fatedier
5bdf530b7e Merge pull request #1045 from fatedier/dev
merge dev -> master
2019-01-15 19:45:00 +08:00
fatedier
5177570da4 Merge pull request #1043 from 442hz/fixup-dashboard-api
frps dashboard api: fixup getProxyStatsByType no data return
2019-01-15 19:37:47 +08:00
荒野無燈
0bd8f9cd9b frps dashboard api: fixup getProxyStatsByType no data return 2019-01-15 19:22:38 +08:00
fatedier
649a2f2457 Merge pull request #1042 from fatedier/dev
release v0.23.0
2019-01-15 16:01:24 +08:00
fatedier
54916793f9 Merge pull request #1041 from fatedier/update
optimize code
2019-01-15 15:58:06 +08:00
fatedier
0b06c1c821 doc: add zsxq pic 2019-01-15 15:27:27 +08:00
fatedier
bbc6f1687d doc: add health check 2019-01-15 11:29:41 +08:00
fatedier
b250342e27 web: remove auth timeout 2019-01-15 00:32:11 +08:00
fatedier
f76deb8898 frps: remove auth timeout 2019-01-15 00:22:13 +08:00
fatedier
611d063e1f server: adjust code structure 2019-01-15 00:11:08 +08:00
fatedier
0c7d778896 frps: optimize code 2019-01-11 14:34:50 +08:00
fatedier
7c21906884 improve kcp shutdown 2018-12-11 15:17:36 +08:00
fatedier
a4106ec4b7 Merge pull request #1006 from fatedier/doc
doc: add conf template
2018-12-11 12:05:23 +08:00
fatedier
655c52f9ce doc: add conf template 2018-12-11 12:01:36 +08:00
fatedier
25cfda5768 conf: support render configure file using environment variables 2018-12-11 11:46:12 +08:00
fatedier
b61cb14c8f remove docker support 2018-12-10 14:21:33 +08:00
fatedier
d5ce4d4916 Merge pull request #1000 from fatedier/dev
bump version to v0.22.0
2018-12-09 22:35:26 +08:00
fatedier
4f0ee5980d Merge pull request #998 from fatedier/health
frpc: support health check
2018-12-09 22:10:13 +08:00
fatedier
146956ac6e bump version to v0.22.0 2018-12-09 22:06:56 +08:00
fatedier
35278ad17f mv folders 2018-12-09 22:06:22 +08:00
fatedier
aea9f9fbcc health: add more ci cases and fix bugs 2018-12-09 21:56:46 +08:00
fatedier
08c17c3247 frpc: support health check 2018-12-07 18:40:17 +08:00
fatedier
6934a18f95 Merge pull request #981 from bighamx/patch-1
Update README_zh.md
2018-11-21 14:56:44 +08:00
SpanishBigHam
5165b0821f Update README_zh.md
fix typo
2018-11-21 14:51:33 +08:00
fatedier
0aec869513 Merge pull request #974 from acrogenesis/patch-1
Fix setting basic auth password doc
2018-11-19 10:55:21 +08:00
Adrian Rangel
826b9db5f2 fix setting basic auth password doc 2018-11-15 22:53:35 -06:00
fatedier
89d1a1fb2b Merge pull request #968 from fatedier/health
support health check and code refactor
2018-11-09 10:06:47 +08:00
fatedier
450e0b7148 Merge pull request #967 from fatedier/fix
update golib, fix #959
2018-11-08 09:50:35 +08:00
fatedier
a1ac002694 update golib, fix #959 2018-11-07 21:10:47 +08:00
fatedier
951d33d47c Merge pull request #966 from fatedier/new_health
refactor code
2018-11-06 23:16:56 +08:00
fatedier
b33ea9274c client/control: refactor code 2018-11-06 18:35:05 +08:00
fatedier
3b3f3dc2b5 Merge pull request #947 from HaraldNordgren/master
Bump Travis versions
2018-10-29 10:16:58 +08:00
fatedier
ec0b59732c Merge pull request #955 from muesli/readme-fixes
Grammar fixes and improved README
2018-10-25 10:43:02 +08:00
Christian Muehlhaeuser
bae1ecdc69 Grammar fixes and improved README
I think this reads nicer and more accurately describes the HTTP reverse proxying.
2018-10-25 00:57:50 +02:00
Harald Nordgren
5c2ab5a749 Bump Travis versions 2018-10-19 21:01:21 +02:00
fatedier
1a8ac148ca fix xtcp visitor panic 2018-10-18 13:55:51 +08:00
fatedier
698219b621 frpc: support health check 2018-09-11 18:33:02 +08:00
fatedier
229740524e Merge pull request #891 from mark86092/bump-docker-to-1-10
Upgrade dockerfile golang to 1.10
2018-08-12 22:04:49 +08:00
Yen-chi Chen
cbeeac06a5 Upgrade golang to 1.10 2018-08-12 15:03:48 +08:00
fatedier
66a69f873f Merge pull request #890 from fatedier/dev
bump version to v0.21.0
2018-08-12 12:10:16 +08:00
fatedier
fb13774457 version to v0.21.0 2018-08-12 12:04:12 +08:00
fatedier
f14ed87b29 Merge pull request #886 from fatedier/websocket
Connect protocol support websocket
2018-08-10 16:36:02 +08:00
fatedier
07623027bc websocket: fix 2018-08-10 16:31:49 +08:00
fatedier
941ac25648 fix ci 2018-08-10 12:02:38 +08:00
fatedier
f645082d72 vendor: add package golang.org/x/net/websocket 2018-08-10 11:49:33 +08:00
fatedier
7793f55545 websocket: update muxer for websocket 2018-08-10 11:45:48 +08:00
fatedier
ca88b07ecf optimize 2018-08-08 11:18:38 +08:00
fatedier
6e305db4be Merge pull request #882 from 235832289/master
Fix the problem of long connection for more than 30 seconds and disconnection of the server
2018-08-08 11:03:08 +08:00
itcode
9bb08396c7 Fix the problem of long connection for more than 30 seconds and disconnection of the server 2018-08-08 10:52:08 +08:00
fatedier
64136a3b3e Merge pull request #875 from jettyu/jettyu-websocket
websocket protocol
2018-08-06 15:20:09 +08:00
FishFish
b8037475ed websocket protocol 2018-08-05 12:55:31 +08:00
fatedier
082447f517 frpc: support health check 2018-07-16 01:21:29 +08:00
fatedier
cc6486addb add more cmd test 2018-07-12 16:49:16 +08:00
fatedier
57417c83ae add ci case of reload and reconnect 2018-07-12 15:23:34 +08:00
fatedier
d74b45be5d more ci 2018-07-12 00:31:21 +08:00
fatedier
0d02f291e3 refactor ci test 2018-07-11 23:27:47 +08:00
fatedier
42ee536dae add module comments for vgo 2018-06-27 11:46:53 +08:00
fatedier
c33b5152e7 split visitors from proxies and add health check config 2018-06-25 18:22:35 +08:00
fatedier
b6c219aa97 Merge pull request #814 from neohe/dev
update xtcp log info
2018-06-08 22:51:12 +08:00
Neo He
bbc36be052 update xtcp log info 2018-06-08 21:27:58 +08:00
fatedier
f5778349d5 config: fix server_name not using user as prefix, fix #804 2018-06-08 14:44:19 +08:00
fatedier
71603c6d0b Merge pull request #807 from kac-/allowPorts
cmd: frps: allow_ports option
2018-06-08 10:52:05 +08:00
kac-
e64fcce417 cmd: frps: allow_ports option 2018-06-06 18:25:55 +02:00
fatedier
629f2856b1 Merge pull request #801 from fatedier/dev
bump version v0.20.0
2018-06-01 00:15:22 +08:00
fatedier
aeb9f2b64d update golib 2018-05-29 02:08:41 +08:00
fatedier
85dd41c17b Merge pull request #795 from fatedier/lb
support lb
2018-05-26 22:42:03 +08:00
fatedier
102408d37f doc: load balancing 2018-05-26 02:28:13 +08:00
fatedier
495b577819 update group ci 2018-05-23 14:39:12 +08:00
fatedier
f56b49ad3b new feature: load balancing for tcp proxy 2018-05-22 23:59:35 +08:00
fatedier
cb1bf71bef Merge pull request #787 from lonwern/arm64
build linux_arm64 package
2018-05-21 21:43:16 +08:00
fatedier
b9f062bef2 support lb 2018-05-21 21:22:10 +08:00
fatedier
490019fb51 update vendor, fix #788 2018-05-21 21:09:18 +08:00
fatedier
2e497274ba add ci for setting headers 2018-05-20 23:55:22 +08:00
fatedier
cf4136fe99 Merge pull request #786 from fatedier/header
support setting headers in http request
2018-05-20 23:44:19 +08:00
fatedier
b1e9cff622 doc: about headers 2018-05-20 23:41:15 +08:00
fatedier
db2d1fce76 http: support setting headers 2018-05-20 23:22:07 +08:00
lonwern
8579de9d3f build linux_arm64 package 2018-05-20 23:05:29 +08:00
fatedier
0c35273759 Merge pull request #783 from fatedier/stcp
frps dashboard add stcp
2018-05-20 19:09:43 +08:00
fatedier
6eb8146334 frps dashboard add stcp 2018-05-20 19:06:05 +08:00
fatedier
da78e3f52e Merge pull request #781 from fatedier/dev
bump version to 0.19.1
2018-05-19 23:10:17 +08:00
fatedier
e1918f6396 frps add '-t' to set token 2018-05-18 10:58:08 +08:00
fatedier
ad1e32fd2d fix panic error when vhost_http_port is not set but there is a http
proxy, fix #776
2018-05-18 00:21:11 +08:00
fatedier
3726f99b04 fix ci 2018-05-17 00:44:53 +08:00
fatedier
c8a7405992 version to 0.19.1 2018-05-17 00:17:22 +08:00
fatedier
040d198e36 Merge pull request #773 from fatedier/nat
return error quickly if nathole make error
2018-05-17 00:10:53 +08:00
fatedier
1a6cbbb2d2 return error quickly if nathole make error 2018-05-17 00:07:56 +08:00
fatedier
ea79e03bd0 Merge pull request #772 from fatedier/web
update dashboard ui
2018-05-16 23:56:12 +08:00
fatedier
3e349455a0 commands for xtcp, stcp add 'bind_port', fix #767 2018-05-16 23:45:44 +08:00
fatedier
c7a457a045 update web assets 2018-05-16 03:34:43 +08:00
fatedier
0b0d5c982e update web 2018-05-16 02:51:02 +08:00
fatedier
c4f873c07a update web package 2018-05-14 16:17:13 +08:00
fatedier
01b1df2b91 update cmd help info 2018-05-14 15:01:16 +08:00
fatedier
f1bea49314 Merge pull request #766 from fatedier/dev
bump version to 0.19.0
2018-05-14 11:08:46 +08:00
fatedier
2ffae3489b dashboard: more params 2018-05-11 17:25:01 +08:00
fatedier
96b94d9164 dashbaord_api: more info 2018-05-11 17:14:16 +08:00
fatedier
76b04f52d1 udpate doc 2018-05-11 16:54:36 +08:00
fatedier
97db0d187a Merge pull request #760 from fatedier/vendor
move some packages to golib (https://github.com/fatedier/golib)
2018-05-11 16:47:03 +08:00
fatedier
d9aadab4cb vendor: add golib/msg 2018-05-11 16:42:08 +08:00
fatedier
a0fe2fc2c2 vendor: models/msg 2018-05-11 16:37:44 +08:00
fatedier
1464836f05 logs panic debug strace info 2018-05-11 12:05:37 +08:00
fatedier
b2a2037032 change accept connection error loglevel to debug 2018-05-11 10:35:16 +08:00
fatedier
071cbf4b15 vendor: update 2018-05-09 01:05:14 +08:00
fatedier
20fcb58437 vendor: add package golib/net 2018-05-09 00:23:42 +08:00
fatedier
a27e3dda88 vendor: update shutdown 2018-05-08 23:51:13 +08:00
fatedier
1dd7317c06 vendor: add package io 2018-05-08 23:42:04 +08:00
fatedier
58a54bd0fb Merge pull request #755 from king6cong/master
typo fix
2018-05-08 17:12:02 +08:00
king6cong
caec4982cc typo fix 2018-05-08 17:02:49 +08:00
fatedier
dd8f788ca4 use dep instead of glide 2018-05-08 02:40:11 +08:00
fatedier
8a6d6c534a vendor: udpate 2018-05-08 02:13:30 +08:00
fatedier
39089cf262 Merge pull request #750 from fatedier/doc
doc: update
2018-05-07 16:48:52 +08:00
fatedier
55800dc29f doc: update 2018-05-07 16:46:04 +08:00
fatedier
04560c1896 web: translate web interface completely to English, fix #680 2018-05-06 23:39:33 +08:00
fatedier
178efd67f1 Merge pull request #746 from fatedier/mux
http port and https port can be same with frps bind_port
2018-05-06 22:54:30 +08:00
fatedier
6ef5fb6391 Merge pull request #744 from wujingquan/patch-1
Update frps_full.ini
2018-05-06 22:53:59 +08:00
fatedier
1ae43e4d41 plugin: update http_proxy 2018-05-06 22:47:26 +08:00
fatedier
5db605ca02 frps: vhost_http_port and vhost_https_port can be same with frps bind
port
2018-05-06 20:25:52 +08:00
Touch
e087301425 Update frps_full.ini 2018-05-06 04:08:36 +08:00
fatedier
f45283dbdb disable yamux default log 2018-05-05 00:09:39 +08:00
fatedier
b0959b3caa bump version to v0.19.0 2018-05-04 23:14:38 +08:00
fatedier
c5c89a2519 doc: update 2018-05-04 23:12:23 +08:00
fatedier
bebd1db22a Merge pull request #740 from fatedier/socks5
frpc: support connectiong frps by socks5 proxy
2018-05-04 19:15:11 +08:00
fatedier
cd37d22f3b vendor: udpate golang.org/x/net 2018-05-04 18:37:43 +08:00
fatedier
30af32728a frpc: support connectiong frps by socks5 proxy 2018-05-04 18:36:38 +08:00
fatedier
60ecd1d58c cmd: change http_user and http_pwd default value to empty 2018-05-03 10:20:09 +08:00
fatedier
a60be8f562 update Makefile 2018-05-03 00:32:05 +08:00
fatedier
c008b14d0f Merge pull request #735 from fatedier/dev
bump version to v0.18.0
2018-05-02 22:37:09 +08:00
fatedier
853892f3cd change version to v0.18.0 2018-05-02 22:09:15 +08:00
fatedier
e43f9f5850 Merge pull request #734 from fatedier/mux
use yamux instead of smux
2018-05-02 21:57:18 +08:00
fatedier
d5f30ccd6b Merge pull request #726 from shuaihanhungry/develop
do not ignore config parsing error
2018-05-02 21:51:02 +08:00
hanshuai
b87df569e7 do not ignore config parsing error 2018-05-02 20:40:33 +08:00
fatedier
976cf3e9f8 use yamux instead of smux 2018-04-25 02:42:00 +08:00
fatedier
371c401f5b Merge pull request #720 from fatedier/dev
bump version to v0.17.0
2018-04-24 01:58:57 +08:00
fatedier
69919e8ef9 Merge pull request #719 from fatedier/doc
update
2018-04-24 01:55:19 +08:00
fatedier
9abbe33790 typo 2018-04-24 01:51:52 +08:00
fatedier
4a5c00286e doc: update 2018-04-24 01:28:25 +08:00
fatedier
dfb892c8f6 Merge pull request #718 from fatedier/cmd
more cmds
2018-04-23 03:07:14 +08:00
fatedier
461c4c18fd update doc 2018-04-23 03:04:33 +08:00
fatedier
00b9ba95ae frpc: support specify default dns server, close #700 2018-04-23 02:59:40 +08:00
fatedier
c47aad348d fix 2018-04-23 02:40:25 +08:00
fatedier
4cb4da3afc add package github.com/spf13/cobra 2018-04-23 02:35:50 +08:00
fatedier
c1f57da00d update packages 2018-04-23 02:31:00 +08:00
fatedier
fe187eb8ec remove package github.com/docopt/docopt-go 2018-04-23 02:15:01 +08:00
fatedier
0f6f674a64 cmd: support more cli command 2018-04-23 02:00:25 +08:00
fatedier
814afbe1f6 Merge pull request #688 from miwee/dashboard_api_client_status
dashboard_api for getting a client status by name
2018-04-10 17:57:26 +08:00
miwee
3fde9176c9 dashboard_api for getting a client status by name 2018-04-04 12:07:20 +05:30
fatedier
af7cca1a93 Merge pull request #685 from toby1991/dev
fix https://github.com/fatedier/frp/issues/684
2018-04-04 11:32:48 +08:00
toby1991
7dd28a14aa fix https://github.com/fatedier/frp/issues/684
#684 Cannot build from Dockerfile
2018-04-04 11:06:47 +08:00
fatedier
1325c59a4c Merge pull request #672 from fatedier/dev
bump version to v0.16.1
2018-03-21 18:09:39 +08:00
fatedier
82dc1e924f vhost: typo fix 2018-03-21 18:06:43 +08:00
fatedier
3166bdf3f0 bump version to v0.16.1 2018-03-21 18:00:31 +08:00
fatedier
8af70c8822 update go version to go1.10 2018-03-21 11:52:11 +08:00
fatedier
87763e8251 Merge pull request #670 from fatedier/new
some fix
2018-03-21 11:45:48 +08:00
fatedier
e9241aeb94 udp proxy: fix #652 2018-03-19 20:22:15 +08:00
fatedier
2eaf134042 Merge pull request #646 from travisghansen/dev
build freebsd packages
2018-02-27 23:14:15 +08:00
Travis Glenn Hansen
1739e012d6 build freebsd packages 2018-02-26 21:00:55 -07:00
fatedier
9e8980429f typo 2018-02-07 11:39:30 +08:00
fatedier
1d0865ca49 statsConn: avoid repetition of close function 2018-02-01 11:15:35 +08:00
fatedier
5c9909aeef typo 2018-01-30 22:07:16 +08:00
fatedier
456ce09061 Merge pull request #630 from fatedier/dev
bump version to v0.16.0
2018-01-30 00:04:02 +08:00
fatedier
ffc13b704a update version 2018-01-30 00:00:05 +08:00
fatedier
5d239127bb Merge pull request #629 from fatedier/new
new feature
2018-01-29 23:58:55 +08:00
fatedier
9b990adf96 frpc: add proxy status 'wait start' 2018-01-29 23:51:46 +08:00
fatedier
44e8108910 ci: add test case for range ports mapping 2018-01-29 23:13:10 +08:00
fatedier
1c35e9a0c6 doc: update 2018-01-29 23:05:17 +08:00
fatedier
8e719ff0ff frps: new params max_ports_per_client 2018-01-26 14:56:55 +08:00
fatedier
637ddbce1f frpc: udpate proxies check and start logic 2018-01-26 00:23:48 +08:00
fatedier
ce8fde793c new feature: range section for mapping range ports 2018-01-25 23:05:07 +08:00
fatedier
eede31c064 doc: about static_file plugin 2018-01-24 23:27:03 +08:00
fatedier
41c41789b6 plugin: socks5 support user password auth, close #484 2018-01-24 23:06:38 +08:00
fatedier
68dfc89bce plugin: new plugin static_file for getting files by http protocol 2018-01-24 17:49:13 +08:00
fatedier
8690075c0c Merge pull request #616 from fatedier/dev
bump version to v0.15.1
2018-01-23 17:33:48 +08:00
fatedier
33d8816ced version: to v0.15.1 2018-01-23 17:28:00 +08:00
fatedier
90cd25ac21 Merge pull request #615 from fatedier/ws
fix websocket and plugin http_proxy
2018-01-23 17:25:32 +08:00
fatedier
ff28668cf2 ci: add plugin http_proxy test case 2018-01-23 17:11:59 +08:00
fatedier
a6f2736b80 fix plugin http_proxy error 2018-01-23 16:31:59 +08:00
fatedier
902f6f84a5 ci: add test for websocket 2018-01-23 14:49:04 +08:00
fatedier
cf9193a429 newhttp: support websocket 2018-01-23 01:29:52 +08:00
fatedier
3f64d73ea9 ci: add subdomain test case 2018-01-22 14:16:46 +08:00
fatedier
a77c7e8625 server: change MIN_PORT from 1024 to 1 2018-01-22 11:48:31 +08:00
fatedier
14733dd109 Merge pull request #608 from fatedier/dev
bump version to 0.15.0
2018-01-18 16:49:55 +08:00
fatedier
74b75e8c57 Merge pull request #607 from fatedier/doc
doc: update
2018-01-18 16:46:33 +08:00
fatedier
63e6e0dc92 doc: update 2018-01-18 16:43:03 +08:00
fatedier
4d4a738aa9 web: update assets 2018-01-18 15:26:30 +08:00
fatedier
1ed130e704 version: to v0.15.0 2018-01-18 15:08:16 +08:00
fatedier
2e773d550b Merge pull request #606 from fatedier/test
tests: more ci case
2018-01-18 15:04:03 +08:00
fatedier
e155ff056e tests: more ci case 2018-01-18 14:53:44 +08:00
fatedier
37210d9983 Merge branch 'dev' of github.com:fatedier/frp into dev 2018-01-18 00:46:21 +08:00
fatedier
338d5bae37 fix panic when using socks5 plugin with encryption and compression, fix #446 2018-01-18 00:45:11 +08:00
fatedier
3e62198612 Merge pull request #604 from fatedier/http
fix new http no traffic stats, fix #590
2018-01-17 23:28:48 +08:00
fatedier
4f7dfcdb31 fix new http no traffic stats, fix #590 2018-01-17 23:17:15 +08:00
fatedier
5b08201e5d Merge pull request #603 from fatedier/test
add test cases and new feature assgin a random port if remote_port is 0
2018-01-17 22:45:02 +08:00
fatedier
b2c846664d new feature: assign a random port if remote_port is 0 in type tcp and
udp
2018-01-17 22:18:34 +08:00
fatedier
3f6799c06a add remoteAddr in NewProxyResp message 2018-01-17 15:01:26 +08:00
fatedier
9a5f0c23c4 fix ci 2018-01-17 01:18:40 +08:00
fatedier
afde0c515c packages: add package github.com/rodaine/table 2018-01-17 01:15:34 +08:00
fatedier
584e098e8e frpc: add status command 2018-01-17 01:09:33 +08:00
fatedier
37395b3ef5 Merge pull request #596 from NemoAlex/patch-2
Use sans-serif font in web
2018-01-10 10:36:28 +08:00
NemoAlex
43fb3f3ff7 Use sans-serif font in web 2018-01-08 13:56:51 +08:00
fatedier
82b127494c Merge pull request #576 from gtt116/master
Close connection if frpc can't connection to local server
2017-12-26 14:51:02 +08:00
gtt116
4d79648657 Close connection if frpc can't connection to local server
Now, when frpc can't connect to local server it leaves the connection alone, the patch fix it.

Fixed #575
2017-12-26 14:39:07 +08:00
fatedier
3bb404dfb5 more test case 2017-12-18 19:35:09 +08:00
fatedier
ff4bdec3f7 add test case 2017-12-16 23:59:46 +08:00
fatedier
69f8b08ac0 update role error log info 2017-12-16 21:56:13 +08:00
fatedier
d873df5ca8 let role default value to 'server' 2017-12-15 11:40:08 +08:00
fatedier
a384bf5580 Merge pull request #564 from fatedier/dev
bump version to v0.14.1
2017-12-14 22:28:25 +08:00
fatedier
92046a7ca2 Merge pull request #561 from fatedier/http
improve http vhost package
2017-12-13 23:48:18 +08:00
fatedier
4cc5ddc012 newhttp support BasicAuth 2017-12-13 23:44:27 +08:00
fatedier
46358d466d support encryption and compression in new http reverser proxy 2017-12-13 04:28:58 +08:00
fatedier
7da61f004b improve http vhost package 2017-12-13 03:27:43 +08:00
fatedier
63037f1c65 typo fix 2017-12-11 22:46:45 +08:00
fatedier
cc160995da improve error role log info 2017-12-11 16:21:17 +08:00
fatedier
de48d97cb2 fix kcp port print error 2017-12-11 01:36:47 +08:00
fatedier
1a6a179b68 visitor: fix panic 2017-12-05 22:26:53 +08:00
fatedier
3a2946a2ff Merge pull request #549 from fatedier/dev
bump version to v0.14.0
2017-12-05 01:42:00 +08:00
fatedier
ae9a4623d9 Merge pull request #548 from fatedier/doc
update doc and fix vistor -> visitor
2017-12-05 01:38:03 +08:00
fatedier
bd1e9a3010 update doc and fix vistor -> visitor 2017-12-05 01:34:33 +08:00
fatedier
92fff5c191 Merge pull request #539 from timerever/dev
add custom dashboard bind address
2017-11-29 10:34:36 +08:00
timerever
8c65b337ca add custom dashboard bind address 2017-11-28 15:56:34 +08:00
fatedier
0f1005ff61 using glide 2017-11-01 16:21:57 +08:00
fatedier
ad858a0d32 prevent sending on a closed channel in vhost package, fix #502 2017-11-01 10:51:30 +08:00
fatedier
1e905839f0 update kcp connection options 2017-10-25 03:02:25 +08:00
fatedier
bf50f932d9 update version to v0.14.0 2017-10-25 02:55:36 +08:00
fatedier
673047be2c Merge pull request #496 from fatedier/0.14
xtcp for p2p communication
2017-10-24 13:54:32 -05:00
fatedier
fa2b9a836c fix xtcp encryption 2017-10-25 02:49:56 +08:00
fatedier
9e0fd0c4ef add packages 2017-10-25 02:29:04 +08:00
fatedier
0559865fe5 support xtcp for making nat hole 2017-10-25 01:27:04 +08:00
fatedier
4fc85a36c2 Merge pull request #486 from xiaox0321/patch-1
Update version.go
2017-10-20 04:12:47 -05:00
xiaox0321
3f1174a519 Update version.go
Optimize duplicate code
2017-10-20 15:58:03 +08:00
fatedier
bcbdfcb99b Merge pull request #473 from Hyduan/master
doc: fix spelling error
2017-09-27 11:55:17 -05:00
Hyduan
df046bdeeb doc: fix spelling error 2017-09-26 21:06:28 +08:00
fatedier
f83447c652 Merge pull request #461 from dvrkps/patch-1
travis: add 1.x to go versions
2017-09-11 06:04:14 -05:00
Davor Kapsa
9ae69b4aac travis: add 1.x to go versions 2017-09-11 12:41:33 +02:00
fatedier
c48a89731a Merge pull request #454 from GeorgeYuen/diamondyuan
add Dockerfile_multiple_build
2017-09-06 01:07:36 -05:00
袁凡迪
36b58ab60c add Dockerfile_multiple_build 2017-09-06 12:51:29 +08:00
fatedier
6320f15a7c typo for default config file name used for frpc 2017-07-19 22:56:12 +08:00
fatedier
066172e9c1 Merge pull request #403 from fatedier/dev
bump version to v0.13.0
2017-07-16 13:20:42 -05:00
fatedier
d5931758b6 fix user in reload command 2017-07-17 02:14:30 +08:00
fatedier
c75c3acd21 Merge pull request #402 from fatedier/doc
update doc for v0.13.0
2017-07-16 13:12:06 -05:00
fatedier
0208ecd1d9 update doc for v0.13.0 2017-07-17 02:09:51 +08:00
fatedier
23e9845e65 Merge pull request #401 from fatedier/0.13
merge 0.13
2017-07-14 23:13:42 +08:00
fatedier
2b1ba3a946 update conf 2017-07-13 12:01:46 +08:00
fatedier
ee9ddf52cd frpc: support --reload command 2017-07-13 02:30:25 +08:00
fatedier
d246400a71 frpc: add admin server for reload configure file 2017-07-13 02:20:49 +08:00
fatedier
f63a4f0cdd frps: new parameter 'proxy_bind_addr' 2017-07-05 01:40:01 +08:00
fatedier
b743b5aaed Merge pull request #390 from lukazh/patch-1
Update README.md
2017-07-05 01:27:41 +08:00
Lukaz
9d9416ab94 Update README.md
fix a typo
2017-07-04 23:05:24 +08:00
fatedier
c081df40e1 vendor: add github.com/armon/go-socks5 2017-07-01 16:09:09 +08:00
fatedier
fe32a7c4bb doc: update 2017-07-01 16:03:13 +08:00
fatedier
7bb8c10647 plugin: add socks5 plugin 2017-07-01 15:56:48 +08:00
fatedier
0752508469 vhost: a bug fix of reading request 2017-07-01 12:13:44 +08:00
fatedier
4cc1663a5f vhost: add real ip in first request of one connection
1. fix #248 host_header_rewrite bug
2. close #270, #127
2017-07-01 01:54:37 +08:00
fatedier
b55a24a27e update mutex used in frpc control 2017-06-27 23:31:02 +08:00
fatedier
aede4e54f8 close all proxies if protocol = kcp 2017-06-27 01:59:30 +08:00
fatedier
b811a620c3 vhost: fix 404 page 2017-06-26 22:24:47 +08:00
fatedier
07fe05a9d5 update version to v0.13.0 2017-06-26 20:57:10 +08:00
fatedier
171bc8dd22 new proxy type: stcp(secret tcp) 2017-06-26 03:02:33 +08:00
fatedier
9c175d4eb5 Merge pull request #380 from IanSmith123/fixbug
fix backquote
2017-06-24 13:19:43 +08:00
Iansmith's win10
9f736558e2 fix backquote 2017-06-24 12:17:09 +08:00
fatedier
8f071dd2c2 Merge pull request #375 from fangqiuming/fangqiuming-patch-1
Fix dockerfile
2017-06-21 18:50:47 +08:00
方秋鸣
bcaf51a6ad Fix dockerfile
Fix incorrect filenames
2017-06-21 14:46:24 +08:00
fatedier
ad3cf9a64a Merge pull request #372 from fatedier/dev
bump verson to v0.12.0
2017-06-19 21:36:51 +08:00
fatedier
e3fc73dbc5 update doc 2017-06-17 18:01:08 +08:00
fatedier
f884e894f2 Merge pull request #363 from fatedier/doc
update doc
2017-06-13 12:44:47 -05:00
fatedier
d57ed7d3d8 update doc 2017-06-14 01:40:20 +08:00
fatedier
a2c318d24c update kcp mode 2017-06-13 23:36:10 +08:00
fatedier
32f8745d61 Merge pull request #360 from fatedier/doc
update doc
2017-06-11 13:46:33 -05:00
fatedier
66120fe49d update doc 2017-06-12 02:41:25 +08:00
fatedier
fca7f42b37 msg: new message CloseProxy 2017-06-11 17:22:05 +08:00
fatedier
5b303f5148 vhost: return 404 not found page if domain doesn't exist 2017-06-11 16:23:00 +08:00
fatedier
2a044c9d6d http_proxy: fix error using encryption or compression 2017-06-09 02:13:24 +08:00
fatedier
70e2aee46d format 2017-06-09 01:33:57 +08:00
fatedier
6742fa2ea8 io: WithCompression resuse snappy.Reader and snappy.Writer 2017-06-08 00:57:33 +08:00
fatedier
511503d34c io.Copy use pool buffer 2017-06-06 18:48:40 +08:00
fatedier
1eaf17fd05 fix ci 2017-06-06 01:39:06 +08:00
fatedier
04f4fd0a81 proto/tcp: fix unexpected close function, fix #332 2017-06-05 23:52:24 +08:00
fatedier
3a4d769bb3 update packages 2017-06-04 20:52:42 +08:00
fatedier
84341b7fcc vendor: add kcp-go package 2017-06-04 20:07:03 +08:00
fatedier
80ba931326 support protocol kcp 2017-06-04 19:56:21 +08:00
fatedier
7ebcc7503a Merge pull request #351 from fatedier/dev
update ISSUE_TEMPLATE
2017-06-03 10:50:40 -05:00
fatedier
74cf57feb3 update ISSUE_TEMPLATE 2017-06-03 23:48:38 +08:00
fatedier
712afed0ab Merge pull request #344 from fatedier/dev
bump version to 0.11.0
2017-06-01 10:46:16 -05:00
fatedier
e29a1330ed dashboard: add proxy start and close time 2017-05-31 02:21:15 +08:00
fatedier
44971c7918 dashboard: use gzip for static files, resolve #333 2017-05-31 01:44:18 +08:00
fatedier
7bc6c72844 dashboard: fix dashboard auth error, fix #339 2017-05-31 01:07:51 +08:00
fatedier
93461e0094 Merge pull request #340 from fatedier/http_proxy
plugin: add http_proxy
2017-05-30 03:12:16 -05:00
fatedier
03d55201b2 plugin: add http_proxy 2017-05-30 16:10:21 +08:00
fatedier
e6d82f3162 Merge pull request #336 from bingtianbaihua/refactoring
add http proxy
2017-05-27 06:27:53 -05:00
ambitioner
1af6276be9 modify 2017-05-26 21:20:54 +08:00
ambitioner
d1f5ec083a add http proxy
add http proxy

add proxy code

add proxy
2017-05-26 20:57:03 +08:00
fatedier
716ec281f6 net: add WrapReadWriteCloserConn 2017-05-26 14:17:46 +08:00
fatedier
67bfae5d23 dashboard: add frps version in Overview page 2017-05-26 12:05:39 +08:00
fatedier
f0dc3ed47b metric: clear useless proxy statistics data 2017-05-26 02:00:00 +08:00
fatedier
08b0885564 Merge pull request #335 from fatedier/start
client: add start params
2017-05-24 12:50:23 -05:00
fatedier
49b503c17b client: add start params
Proxy names specified in 'start' params divided by ',' will be started.
If it is empty or not defined, all proxies will be started.
2017-05-25 01:45:38 +08:00
fatedier
150682ec63 Merge pull request #334 from fatedier/start
client: add login_fail_exit params, default is true
2017-05-24 12:16:02 -05:00
fatedier
4dc96f41c9 client: add login_fail_exit params, default is true
if login_fail_exit is false, when frpc first login to server failed, it
    will continues relogin to server every 30 seconds.
2017-05-25 01:10:58 +08:00
fatedier
6c13b6d37a net: fix HTTP_PROXY include escape characters error, fix #275 2017-05-23 02:10:36 +08:00
fatedier
1c04de380d Merge pull request #328 from fatedier/plugin
new feature plugin and unix domian socket plugin
2017-05-22 01:12:14 -05:00
fatedier
738e5dad22 new feature plugin and unix domian socket plugin 2017-05-22 00:15:18 +08:00
fatedier
6d81e4c8c6 Merge pull request #325 from fatedier/dev
merge dev to master
2017-05-20 00:39:45 -05:00
fatedier
faf584e1dd metric: fix statistics error 2017-05-19 18:38:31 +08:00
fatedier
ba6afd5789 doc: update 2017-05-19 02:59:51 +08:00
fatedier
11260389a1 Merge pull request #322 from fatedier/dev
bump version to 0.10.0
2017-05-18 13:47:43 -05:00
fatedier
b8082e6e08 Merge pull request #321 from fatedier/doc
doc: update
2017-05-18 13:31:46 -05:00
fatedier
7957572ced update 2017-05-19 02:20:10 +08:00
fatedier
c2ff37d0d8 Merge pull request #320 from yuyulei/dev
change allow ports from map to array
2017-05-18 13:05:33 -05:00
fatedier
c67f9d5e76 doc: update 2017-05-19 01:54:57 +08:00
yuyulei
1cc61b60f9 fixs import 2017-05-19 00:00:42 +08:00
yuyulei
9c38baeb9e add ut 2017-05-18 23:58:58 +08:00
yuyulei
84465a7463 change allow ports from map to array 2017-05-18 23:58:46 +08:00
fatedier
3fe50df200 doc: update 2017-05-18 01:59:46 +08:00
fatedier
93d86ca635 vendor: add github.com/xtaci/smux 2017-05-17 22:48:41 +08:00
fatedier
b600a07ec0 support tcp stream multiplexing by smux 2017-05-17 17:47:20 +08:00
fatedier
a5f06489cb update 2017-05-17 16:02:31 +08:00
fatedier
2883d70ea9 dashboard: don't check authentication if user and password is empty 2017-05-15 21:30:13 +08:00
fatedier
3f17837a2c web: support http basic auth in dashboard 2017-05-15 21:18:06 +08:00
fatedier
fd268b5082 Merge pull request #316 from fatedier/refactoring
Refactoring
2017-05-14 11:15:52 -05:00
fatedier
69b09eb8a2 udp: add heartbeat in udp work connection 2017-05-15 00:08:21 +08:00
fatedier
a84dd05351 add connection read timeout 2017-05-10 00:46:42 +08:00
fatedier
71f7caa1ee add more log info 2017-04-25 00:34:14 +08:00
fatedier
5360febd72 update log module 2017-04-21 16:44:51 +08:00
fatedier
1b70f0c4fd update log module 2017-04-21 00:02:21 +08:00
fatedier
5c75efa222 update dashboard fetch api 2017-03-28 01:08:04 +08:00
fatedier
ab4a53965b test: more case 2017-03-28 00:27:30 +08:00
fatedier
a0c83bdb78 test: add test case 2017-03-27 23:46:38 +08:00
fatedier
30aeaf968e fix heartbeat error 2017-03-27 17:25:25 +08:00
fatedier
58d0d41501 fix 2017-03-27 03:53:21 +08:00
fatedier
6a95a63fd4 metric: update date_counter 2017-03-27 03:39:32 +08:00
fatedier
aa185eb9f3 update static files for dashboard 2017-03-27 02:21:37 +08:00
fatedier
d8683a0079 new frps dashboard 2017-03-27 02:15:31 +08:00
fatedier
8b2cde3a30 update for metric 2017-03-27 01:39:05 +08:00
fatedier
634e048d0c dep: add httprouter 2017-03-23 02:06:04 +08:00
fatedier
a4fece3f51 api: add server web api for statistics 2017-03-23 02:01:25 +08:00
fatedier
9e683fe446 update basic auth method 2017-03-21 21:36:48 +08:00
fatedier
54bbfe26b0 support udp 2017-03-13 02:44:47 +08:00
fatedier
a1023fdfc2 add more log 2017-03-12 03:22:35 +08:00
fatedier
b02e1007fb support more proxy type 2017-03-12 01:08:33 +08:00
fatedier
f90028cf96 Use encryption in frp protocol. 2017-03-10 01:44:50 +08:00
fatedier
f83a2a73ab test: update 2017-03-10 00:52:32 +08:00
fatedier
307b74cc13 build: add go1.8 and remove go1.5 2017-03-09 22:55:30 +08:00
fatedier
f00a28598f github: add issue template 2017-03-09 22:53:32 +08:00
fatedier
6ee0b25782 update 2017-03-09 22:45:44 +08:00
fatedier
88083d21e8 start refactoring 2017-03-09 22:44:42 +08:00
fatedier
a22440aade start refactoring 2017-03-09 22:28:31 +08:00
fatedier
b006540141 Merge pull request #266 from fsyxhua/master
Disable the "CGO" mode ,fix the "./frpc not found" problem,fix #262
2017-02-24 02:59:49 -06:00
fatedier
e655f07674 Merge branch 'dev' into master 2017-02-24 02:57:47 -06:00
fsyx_hua
aafa96db58 Disable the "CGO" mode ,fix the "./frpc not found" problem,fix #262 2017-02-24 16:42:36 +08:00
fatedier
1325148cd3 build: support 32 bit of mips, remove darwin 32 2017-01-17 23:53:17 +08:00
fatedier
3f9749488a Merge pull request #238 from fatedier/dev
bump version to 0.9.3
2017-01-17 09:18:45 -06:00
fatedier
f9a0d891a1 pool: fix panic caused by sending to closed channel, fix #237 2017-01-17 23:12:52 +08:00
fatedier
92daa45b68 change version to 0.9.3 2017-01-14 00:48:56 +08:00
fatedier
5f20a22b0d Merge pull request #231 from LitleCarl/dev
Fix Bug for issure, fix #227
2017-01-12 23:38:05 -06:00
Tsao
63be94c611 Fix Bug for closing an exist ProxyServer when another ProxyServer with same name comes. 2017-01-13 13:18:22 +08:00
fatedier
694ee44af6 subdomain: subdomain can be configured in frps.ini, fix #220 2017-01-13 02:23:04 +08:00
fatedier
edb97abf50 Merge pull request #217 from fatedier/dev
bump version to 0.9.2
2017-01-08 09:22:28 -06:00
fatedier
0c10279deb doc: add new contributor 2017-01-08 23:18:25 +08:00
fatedier
1f49510e3e udp proxy: fix reconnect error, fix #209 2017-01-05 23:09:17 +08:00
fatedier
1868b3bafb Merge pull request #215 from bingtianbaihua/dev
modified readme.md
2017-01-05 07:39:15 -06:00
bingtianbaihua
a23521885c modified readme.md 2017-01-05 20:04:26 +08:00
fatedier
c80dcd050d update default value of heartbeat_interval and heartbeat_timeout 2017-01-04 23:09:28 +08:00
fatedier
043ab62587 build: update Makefile.cross-compiles 2017-01-04 22:56:08 +08:00
fatedier
a8969b1901 Merge pull request #207 from bingtianbaihua/master
added heartbeat conf
2016-12-30 04:01:02 -06:00
bingtianbaihua
e26285eefc added heartbeat conf 2016-12-30 17:47:34 +08:00
fatedier
299bd7b5cb frps: fix panic caused by frps closing the nil channel, fix #205 2016-12-29 23:49:39 +08:00
fatedier
90d1384bf7 frps: update 2016-12-28 00:56:55 +08:00
fatedier
a5434e31b7 doc: update 2016-12-28 00:40:45 +08:00
fatedier
044bb692dc Merge pull request #201 from fatedier/dev
bump version to 0.9.1
2016-12-27 10:35:14 -06:00
fatedier
34b98dde52 Merge pull request #200 from fatedier/doc
doc: support url routing
2016-12-27 10:32:06 -06:00
fatedier
020f786bf5 doc: support url routing 2016-12-28 00:26:50 +08:00
fatedier
cdcc1240ec vhost: fix a wrong usage of sort.Reverse 2016-12-27 02:52:32 +08:00
fatedier
c2c9f68a00 frps: improve login response message 2016-12-27 01:45:22 +08:00
fatedier
37470c26f0 frps: fix sometimes no response when frpc login to frps, see #142 2016-12-27 01:19:19 +08:00
fatedier
04a4591caa vhost: check host and location for url router 2016-12-27 01:01:39 +08:00
fatedier
8bf61d5e39 doc: add qq group 2016-12-26 12:18:46 +08:00
fatedier
659f84bab2 conf: update 2016-12-26 09:58:58 +08:00
fatedier
9faf4acd62 connection pool: ssh can't work when pool_count is set, fix #193 2016-12-26 01:55:54 +08:00
fatedier
4c3fb22295 modify version to 0.9.1 2016-12-25 14:26:16 +08:00
fatedier
d243f70125 doc: update contributors 2016-12-25 14:25:12 +08:00
fatedier
a56f068f8c subdomain: fix a bug that subdomain is not correct for https, close #194 2016-12-25 14:16:54 +08:00
fatedier
6a6ccc5302 Merge pull request #192 from fatedier/pr_182
http proxy support router by url
2016-12-24 12:41:20 -06:00
fatedier
6f90c3400c update conf 2016-12-25 02:09:01 +08:00
fatedier
eb4f779384 update 2016-12-25 01:53:23 +08:00
fatedier
59a34b81e0 Merge pull request #182 from xuebing1110/dev
[dev] http router by host and url
2016-12-24 09:08:20 -06:00
fatedier
b1d1a7a20a Merge branch 'pr_182' into dev 2016-12-24 08:51:26 -06:00
fatedier
6b34ed4644 update doc 2016-12-21 21:22:10 +08:00
fatedier
dde734c953 Merge pull request #188 from fatedier/dev
bump version to 0.9.0
2016-12-21 00:11:10 -06:00
fatedier
5532881b09 Merge pull request #187 from fatedier/doc
update doc for v0.9.0
2016-12-21 00:04:16 -06:00
fatedier
94ddeebc21 update doc 2016-12-21 12:10:21 +08:00
fatedier
ddbb56ee8f update 2016-12-21 01:07:30 +08:00
fatedier
10fc6c67e0 some fix 2016-12-21 01:01:44 +08:00
fatedier
0573ddcd84 Merge pull request #186 from vashstorm/bugfix
add auth for reload api
2016-12-20 05:20:54 -06:00
vashstorm
5eb5fec761 add auth for reload api 2016-12-20 18:32:17 +08:00
fatedier
52fe721202 frps: fix a panic bug caused by reading map 2016-12-20 01:30:10 +08:00
fatedier
d7d2b72431 Merge pull request #184 from fatedier/fatedier/udp
udp: fix privilege_mode not success for udp type
2016-12-19 09:57:08 -06:00
fatedier
d04d31b39a udp: fix privilege_mode not success for udp type 2016-12-19 23:30:48 +08:00
Pan Hao
d9304d8166 Merge pull request #183 from fatedier/fatedier/udp
support udp type
2016-12-19 07:30:44 -06:00
XueBing
a44be1e2ed set default location empty string array
modified:   src/models/server/config.go
	modified:   src/utils/vhost/router.go

	modified:   src/models/server/config.go
	modified:   src/utils/vhost/router.go
2016-12-19 10:38:41 +08:00
fatedier
2bf1d3e922 conf: update 2016-12-19 01:48:57 +08:00
fatedier
19f349a65e vendor: fix 2016-12-19 01:44:17 +08:00
fatedier
b0e56945cd vendor: update godep conf 2016-12-19 01:33:26 +08:00
fatedier
f2999e3317 support udp type 2016-12-19 01:22:21 +08:00
XueBing
a4c05e6ff9 show "Port" incorrectly in dashboard 2016-12-18 23:43:08 +08:00
XueBing
d93dd82ed9 modify the getListener function && add TPS in dashboard 2016-12-18 23:35:14 +08:00
XueBing
edf4bc431d support the configure of url in "http" section 2016-12-18 22:40:58 +08:00
fatedier
47db75e921 Merge pull request #152 from fatedier/maodanp/subdomain
Maodanp/subdomain
2016-11-09 00:14:33 +08:00
Maodanping
c702355669 frps/control rename authTimeout; add judgement for subdomain 2016-11-08 13:40:40 +08:00
Maodanping
7cc5d03f35 conf/frps.ini modify domain field 2016-11-07 09:51:27 +08:00
Maodanping
54beb19435 frps/control.go remove log info 2016-11-05 14:26:04 +08:00
Maodanping
396e148f80 add subdomain configuration; add conn auth timeout 2016-11-05 14:15:16 +08:00
fatedier
4c69a4810e Merge pull request #112 from moul/patch-1
Grammar fix
2016-09-13 09:43:50 +08:00
Manfred Touron
40e023f5f4 Grammar fix 2016-09-12 23:15:34 +02:00
fatedier
adcb2c1ea5 typo 2016-09-06 17:56:09 +08:00
fatedier
8c497793c5 Merge pull request #101 from fatedier/maodanp/http_auth
Maodanp/http auth
2016-09-05 22:25:01 -05:00
Maodanping
78c6845781 utils/vhost: remove fmt.Printf method 2016-08-29 11:02:59 +08:00
Maodanping
dc5e130d33 utils/vhost: supprot http authentication 2016-08-29 10:53:02 +08:00
fatedier
fbc504dfa3 cmd/frps: fix wrong usage of package docopt
Fixes #98.
2016-08-26 23:00:56 +08:00
fatedier
77f207d69a Merge branch 'master' into dev 2016-08-24 13:49:46 +08:00
fatedier
b65e037b5e Fix conflicts
Conflicts:
	README_zh.md
	src/utils/version/version.go
2016-08-24 13:49:13 +08:00
fatedier
b8a28e945c Merge pull request #94 from fatedier/doc
doc: add donation info
2016-08-24 13:40:55 +08:00
fatedier
0476a85a7d doc: add donation info 2016-08-24 13:36:38 +08:00
fatedier
5661537f7c cmd/frpc: fix a merge typo cause pool_count do not work 2016-08-23 15:59:50 +08:00
fatedier
19f7950485 bump version to 0.8.1
bump version to 0.8.1
2016-08-23 10:37:22 +08:00
fatedier
c21f8ad291 utils/version: bump version to 0.8.1 2016-08-23 10:34:14 +08:00
fatedier
3d6578b15f Merge pull request #90 from fatedier/release0.8
merge some fix
2016-08-23 10:23:44 +08:00
fatedier
899d6837df release v0.8.1
release v0.8.1
2016-08-23 10:22:31 +08:00
fatedier
0e1752b5ce Merge pull request #88 from fatedier/fatedier/wildcard_domains
utils/vhost: support wildcard domains
2016-08-23 01:03:41 +08:00
fatedier
da182ecd81 utils/vhost: support wildcard domains 2016-08-23 00:58:51 +08:00
fatedier
94c7f57949 dep: fix a log file deleted bug caused by beego.logs 2016-08-22 01:08:19 +08:00
fatedier
c8e5096f48 utils/pcrypto: fix the bug of using aes
1. The aes IV needs to be unique, but not secure.
2. The key should be 16, 24 or 32 bytes.

Fixes #85.
2016-08-21 23:28:01 +08:00
fatedier
5079bf01fd doc: update 2016-08-21 00:52:15 +08:00
fatedier
603d7df49a build: add mips64 and mips64le 2016-08-21 00:07:25 +08:00
fatedier
2b1c39e03d cmd/frps: a bug of status display in dashboard, fix #80 2016-08-18 14:52:11 +08:00
fatedier
46ee2f2bc8 utils/conn: typo 2016-08-17 10:07:16 +08:00
fatedier
3d5c3acee0 dockerfile: add dockerfile for alpine 2016-08-16 16:10:04 +08:00
fatedier
41fd4bb673 typo 2016-08-16 15:09:32 +08:00
fatedier
e1e18ba9d6 dockerfile: update 2016-08-16 15:06:48 +08:00
fatedier
ab9eff97a8 doc: update contributing 2016-08-16 14:09:29 +08:00
fatedier
6f40b1a70a build: use golang 1.7 2016-08-16 14:03:19 +08:00
fatedier
87c9b8f548 Merge pull request #78 from moul/add-docker
Initial Docker support
2016-08-15 23:37:53 +08:00
Manfred Touron
3fcf7efc5a Initial Docker support 2016-08-15 17:28:37 +02:00
fatedier
a655f5699b utils/version: change version to 0.9.0 2016-08-15 01:11:37 +08:00
fatedier
09624b56ca Merge pull request #76 from fatedier/fatedier/support_http_proxy
frpc: add support for connecting server through http proxies, see #67
2016-08-15 01:07:50 +08:00
fatedier
e262ac6abd frpc: add support for connecting server through http proxies, see #67 2016-08-15 00:55:22 +08:00
fatedier
47c1a3e52c Merge pull request #74 from se77en/master
Add basic auth to dashboard
2016-08-14 13:55:13 +08:00
Eric Larssen
4dadaac905 Add basic auth to dashboard 2016-08-14 12:45:35 +08:00
fatedier
e1ed6660b0 Merge pull request #71 from ericlarssen/updateDocs
Sentence Spacing in Readme, killed some whitespace.
2016-08-13 23:17:38 +08:00
Eric Larssen
b71b2cf46d Add sentence spacing in readme 2016-08-13 09:32:11 -05:00
fatedier
a0903d4121 release v0.8.0
release v0.8.0
2016-08-12 14:00:54 +08:00
fatedier
b403e4142b Merge pull request #65 from fatedier/0.8doc
0.8doc
2016-08-12 13:58:32 +08:00
fatedier
46716acd8e doc: update 2016-08-12 13:44:25 +08:00
fatedier
c7f85bcdd3 doc: new features 2016-08-12 12:50:12 +08:00
fatedier
ddd2acfe9f models/msg: fix current connections statistics problem 2016-08-12 10:07:02 +08:00
fatedier
e3bf7e2b2b cmd/frps: typo 2016-08-12 00:57:46 +08:00
fatedier
4914472215 assets: update assets 2016-08-12 00:39:39 +08:00
fatedier
5d9300c1e9 utils/conn: support ipv6, fix #62 2016-08-12 00:32:33 +08:00
fatedier
b4a577b0d7 Merge pull request #64 from fatedier/fatedier/assets_to_binary
assets: use statik to compile static files into binary files
2016-08-11 21:58:58 +08:00
fatedier
32d0ce9ea0 assets: use statik to compile static files into binary files 2016-08-11 21:47:26 +08:00
fatedier
2d30a6e8a7 build: remove support for go1.4, add go1.7 2016-08-11 17:03:19 +08:00
fatedier
740691b080 update package beego/logs 2016-08-11 16:32:05 +08:00
fatedier
11fe4b1d8b Godep workspace -> vendor/ 2016-08-11 16:26:21 +08:00
fatedier
c64931fce9 package: use net.http instead of gin 2016-08-11 16:20:59 +08:00
fatedier
d4ecc2218d all: modify import path, change version to v0.8.0 2016-08-11 16:10:44 +08:00
fatedier
9c0ca8675d Merge pull request #63 from fatedier/vashstorm/dashboard_view
provide a simple dashboard webpage
2016-08-11 15:37:15 +08:00
fatedier
5cdb84c666 index.html table show today flow_out and flow_in 2016-08-11 15:31:35 +08:00
Gogs
060277308b index.html: fix duplicate today info bug 2016-08-11 15:23:50 +08:00
vashstorm
31dfd5101f index.html use bar instead of line, fix col merging bug 2016-08-10 21:44:33 +08:00
fatedier
4300169041 assets: optimize static files archetucture 2016-08-10 20:18:36 +08:00
vashstorm
3ab9850871 fix index.html use local css files 2016-08-09 21:28:43 +08:00
panhao
d813b953dd assets:dashboard static resources 2016-08-09 21:17:02 +08:00
fatedier
1da81ad7d3 models/msg: limit single package length 2016-08-07 13:18:54 +08:00
fatedier
3b06d771ac utils/conn: wsarecv error in windows means connection closed? 2016-08-01 15:24:04 +08:00
fatedier
7f386fc042 utils/pool: use sync.pool to reduce the pressure of garbage collection 2016-07-31 03:26:41 +08:00
fatedier
df8edefa56 Merge pull request #55 from fatedier/fatedier/privilege_mode_port
all: privilege_allow_ports can be set in frps for ending abuse of ports
2016-07-31 02:27:06 +08:00
fatedier
ecb6ad4885 all: privilege_allow_ports can be set in frps for ending abuse of ports 2016-07-31 02:17:10 +08:00
fatedier
785dcaad44 Merge branch 'fatedier/connection_pool' into dev 2016-07-29 23:39:48 +08:00
fatedier
fd3c97a0e9 Fix conflicts in fatedier/connection_pool with dev
Conflicts:
	src/frp/cmd/frpc/control.go
	src/frp/cmd/frps/control.go
	src/frp/models/config/config.go
	src/frp/models/server/server.go
2016-07-29 23:38:34 +08:00
fatedier
8f5f0b0a9a all: new feature connection pool 2016-07-29 23:08:00 +08:00
fatedier
452e02adab add host_header_rewrite in frpc.ini to rewrite your requests with a modified Host header 2016-07-26 00:18:19 +08:00
fatedier
d2e1cfa5bc Merge pull request #52 from maodanp/http_hostname
support host rewrite
2016-07-25 20:52:45 +08:00
Maodanping
6dd51e0951 remove the extra filed 2016-07-24 15:08:01 +08:00
Maodanping
e0f2993b70 change the Host value in http request header 2016-07-24 15:00:18 +08:00
fatedier
4067591a4d Merge pull request #50 from fatedier/fatedier/fix_package_loss
frp/models/msg: fix a bug if local service write to socket immediatel…
2016-07-20 16:37:45 +08:00
fatedier
926d0b74a9 utils/vhost: update TcpConn with bufio.Reader 2016-07-20 16:33:42 +08:00
fatedier
4f49458af0 frp/models/msg: fix a bug if local service write to socket immediately every time accept one user connection, fix #20 2016-07-20 16:04:06 +08:00
fatedier
fd6b94908b models/server: add statistics for proxies enabled privilege mode. 2016-07-18 00:30:46 +08:00
fatedier
dee4cbd48c models/metric: sort for metric result 2016-07-18 00:18:40 +08:00
fatedier
9a3564f29c Merge pull request #49 from fatedier/fatedier/statistics
all: add /api/proxies api for statistics info
2016-07-17 21:52:07 +08:00
fatedier
ac09ba3982 all: add /api/proxies api for statistics info 2016-07-17 21:42:21 +08:00
fatedier
a9bf25f255 build: add go 1.6.2 2016-07-12 01:08:08 +08:00
fatedier
6bc05de58e Merge pull request #48 from fatedier/dev
update README.md
2016-07-12 00:18:20 +08:00
fatedier
5265b79957 Merge branch 'master' into dev 2016-07-11 23:45:36 +08:00
fatedier
fefc0a38c3 Merge pull request #47 from fatedier/fatedier/update_doc
update README.md
2016-07-11 23:44:24 +08:00
fatedier
c387138006 doc: update README.md 2016-07-11 23:35:34 +08:00
fatedier
36f8beee3d doc: update README.md 2016-07-11 23:31:35 +08:00
fatedier
366a0c898d Merge pull request #46 from fatedier/dev
merge for new README_zh.md
2016-07-09 13:13:45 +08:00
fatedier
d747f9207e Merge pull request #45 from fatedier/fatedier/update_doc
doc: update README_zh.md
2016-07-09 13:10:51 +08:00
fatedier
5400366036 doc: update README_zh.md 2016-07-09 13:06:20 +08:00
fatedier
9dae7ad6fe cmd/frps: update 2016-07-08 14:19:26 +08:00
fatedier
a4e051d494 cmd/frps: improve the description of login failed 2016-07-08 14:15:26 +08:00
fatedier
28251a8104 Merge pull request #41 from fatedier/dev
release v0.7.0
2016-07-04 15:11:10 +08:00
fatedier
e99357da4e doc: add contributor 2016-07-04 15:08:16 +08:00
fatedier
e580c7b6e6 all: release resources when proxy closed using privilege mode 2016-06-28 00:55:30 +08:00
fatedier
ba74934a1f all: privilege mode update 2016-06-28 00:21:13 +08:00
fatedier
1bad5c6561 Merge pull request #40 from fatedier/fatedier/privilege_mode
support privilege mode
2016-06-26 22:43:02 +08:00
fatedier
f968f3eace all: add new feature privilege mode 2016-06-26 22:36:07 +08:00
fatedier
b14441d5cd utils/vhost: update for vhost_https 2016-06-24 15:43:58 +08:00
fatedier
0a50c3bd82 Merge pull request #39 from fatedier/release0.7
add support for https
2016-06-24 14:09:50 +08:00
fatedier
ef5702213f Merge pull request #38 from fatedier/fatedier/fix_vhost_bug
cmd/frps: fix a bug when vhost_http_port is not set, fix #30
2016-06-24 14:00:50 +08:00
fatedier
c5e4b24f8f all: add use_gzip configure and some improvements, see #28 2016-06-24 13:12:34 +08:00
Pan Hao
1987a399c1 Merge pull request #33 from fatedier/mydev
User can set use aes or gzip
2016-06-14 17:33:32 +08:00
Gogs
ab6c5c813e User can set use aes or gzip 2016-06-14 16:58:53 +08:00
fatedier
51eaec14ab Merge pull request #32 from maodanp/release0.7
add https proto for reverse proxy
2016-06-13 22:43:32 +08:00
Maodanping
f3876d69bb add https proto for reverse proxy 2016-06-13 22:19:24 +08:00
fatedier
817f4463f4 cmd/frps: fix a bug when vhost_http_port is not set, fix #30 2016-06-12 21:57:55 +08:00
fatedier
654981019d release v0.6.0
release v0.6.0
2016-06-07 22:35:56 +08:00
fatedier
740fb05b21 build: remove godep command(can not download packages in golang.org used in latest godep version) 2016-06-07 22:17:37 +08:00
fatedier
e8c830e5c8 all: format 2016-06-07 11:15:59 +08:00
fatedier
2640c0b570 utils/conn: fix a socket error in windows 2016-06-06 11:43:41 +08:00
fatedier
04014bb78f doc: update 2016-06-03 18:15:01 +08:00
fatedier
150c4beef8 cmd/frps: update help info 2016-06-03 17:55:53 +08:00
fatedier
5febee6201 all: add "--reload" command for frps, reload ini file without kill old program 2016-06-03 17:51:45 +08:00
fatedier
ee8786a6b3 models/server: fix one possible error when frps accept too many user connections in a short time 2016-05-19 20:50:19 +08:00
fatedier
d569a60eff cmd/frpc: let cli.StartTunnel async 2016-05-19 15:13:35 +08:00
fatedier
14607b352d utils/version: update version to v0.6.0 2016-05-19 00:36:08 +08:00
fatedier
bc7ad2bb20 update 2016-05-19 00:28:39 +08:00
fatedier
cd59bbdad6 Godeps: add package gin for dashboard 2016-05-19 00:26:16 +08:00
fatedier
f404a0a5ee models/server: add dashboard for frps by gin 2016-05-19 00:04:19 +08:00
fatedier
da7c473288 fetch commits from master
fetch commits from master
2016-05-17 19:21:27 +08:00
fatedier
ea323084ad test: fix 2016-05-17 19:17:04 +08:00
fatedier
c680d87edc test: add function testing case 2016-05-17 19:13:37 +08:00
fatedier
d3c4401473 build: support linux/arm and darwin 2016-05-15 13:30:14 +08:00
fatedier
7a9a675d58 merge dev for fixing bugs when frpc login failed 2016-05-09 00:37:01 +08:00
fatedier
040841db48 cmd/frps/control: fix bug when frpc login failed 2016-05-09 00:15:31 +08:00
fatedier
f804330dbf release v0.5.0 (#24)
[new] Optimize for http services.Support virtual host and custom domain binding.
[new] Support max days of keeping log files.
[fix] Fix a bug when reconnecting.
2016-05-03 22:05:55 +08:00
fatedier
d39d745e43 docker: rm Dockerfile 2016-05-03 21:51:57 +08:00
fatedier
c10321ead6 utils/log: support max days of keeping log files 2016-05-03 21:41:57 +08:00
fatedier
d7797cbd18 doc: update 2016-04-19 19:02:13 +08:00
fatedier
0b9d823168 doc: update 2016-04-19 18:58:55 +08:00
fatedier
deb750652f doc: update 2016-04-19 18:53:48 +08:00
fatedier
14ba38a1b4 doc: update 2016-04-18 15:47:14 +08:00
fatedier
f650d3f330 all: support for virtual host 2016-04-18 15:16:40 +08:00
fatedier
2c39719cc0 Merge branch 'dev' 2016-04-11 17:38:12 +08:00
fatedier
6874688e07 build: for cross-compiles 2016-04-11 17:27:14 +08:00
fatedier
fdd7436736 build: fix 2016-04-11 12:05:24 +08:00
fatedier
0f326449e8 build: fix 2016-04-11 11:58:07 +08:00
fatedier
7c3e00ed28 build: update Makefile to avoid godep warning 2016-04-11 11:21:35 +08:00
fatedier
d5913fc77b all: fix bug when reconnecting 2016-04-07 19:27:39 +08:00
fatedier
2ba84d375a Release v0.3.0
Release v0.3.0
2016-04-06 12:19:31 +08:00
fatedier
6a0d6035cb doc: typo 2016-04-06 11:51:12 +08:00
fatedier
d091e0eac9 doc: update 2016-04-06 11:43:50 +08:00
fatedier
bc176b90f1 all: change passwd to auth_token and improve authentication 2016-04-05 17:18:21 +08:00
fatedier
a729a4fafe pcrypto: update 2016-04-05 16:17:31 +08:00
fatedier
78c770d37d Merge pull request #15 from fatedier/test 2016-04-05 15:43:28 +08:00
fatedier
718e707b77 pcrypto: fix 2016-04-05 15:40:32 +08:00
fatedier
b3ee746be8 optimize compress 2016-04-05 15:25:55 +08:00
fatedier
80fc76da52 all: now messages between frps and frpc can be encryped when use_encryption set true 2016-03-31 18:48:18 +08:00
fatedier
52f99bbc00 all: some optimization 2016-03-31 18:03:44 +08:00
fatedier
45c21b2705 utils/conn: add func joinMore 2016-03-28 11:22:36 +08:00
fatedier
b6212afb03 Merge pull request #11 from vashstorm/new
fix crypto
2016-03-25 14:09:01 +08:00
Gogs
49975c4c1b fix crypto 2016-03-24 15:42:34 +08:00
fatedier
580e75f633 Merge pull request #8 from vashstorm/master
add crypto
2016-03-23 09:59:42 +08:00
Gogs
20afe25ef1 modified: src/frp/models/client/client.go
modified:   src/frp/models/server/server.go
	modified:   src/frp/utils/conn/conn.go
	modified:   src/frp/utils/pcrypto/pcrypto.go
	modified:   src/frp/utils/pcrypto/pcrypto_test.go
2016-03-22 17:18:05 +08:00
fatedier
6e57135533 typo 2016-03-17 17:52:59 +08:00
fatedier
931c102668 fix bug in Dockerfile 2016-03-17 17:47:41 +08:00
fatedier
5700101c0e doc: typo 2016-03-17 11:04:40 +08:00
fatedier
90349a48b0 doc: update version in doc to v0.2.0 2016-03-17 10:49:38 +08:00
754 changed files with 112520 additions and 5111 deletions

32
.github/ISSUE_TEMPLATE vendored Normal file
View 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
.gitignore vendored
View File

@@ -25,6 +25,8 @@ _testmain.go
# Self
bin/
packages/
test/bin/
# Cache
*.swp

View File

@@ -2,11 +2,11 @@ sudo: false
language: go
go:
- 1.4.2
- 1.5.1
- 1.11.x
- 1.12.x
install:
- make
script:
- make test
- make alltest

View File

@@ -1,14 +0,0 @@
FROM golang:1.5
MAINTAINER fatedier
RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[wiki]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini
ADD ./ /usr/share/frp/
RUN cd /usr/share/frp && make
EXPOSE 80
EXPOSE 7000
CMD ["/usr/share/frp/bin/frps -c /usr/share/frps.ini"]

23
Godeps/Godeps.json generated
View File

@@ -1,23 +0,0 @@
{
"ImportPath": "frp",
"GoVersion": "go1.4",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/astaxie/beego/logs",
"Comment": "v1.5.0-9-gfb7314f",
"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5"
},
{
"ImportPath": "github.com/docopt/docopt-go",
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
}
]
}

5
Godeps/Readme generated
View File

@@ -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.

2
Godeps/_workspace/.gitignore generated vendored
View File

@@ -1,2 +0,0 @@
/pkg
/bin

View File

@@ -1,95 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// 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 logs
import (
"encoding/json"
"log"
"os"
"runtime"
)
type Brush func(string) string
func NewBrush(color string) Brush {
pre := "\033["
reset := "\033[0m"
return func(text string) string {
return pre + color + "m" + text + reset
}
}
var colors = []Brush{
NewBrush("1;37"), // Emergency white
NewBrush("1;36"), // Alert cyan
NewBrush("1;35"), // Critical magenta
NewBrush("1;31"), // Error red
NewBrush("1;33"), // Warning yellow
NewBrush("1;32"), // Notice green
NewBrush("1;34"), // Informational blue
NewBrush("1;34"), // Debug blue
}
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
type ConsoleWriter struct {
lg *log.Logger
Level int `json:"level"`
}
// create ConsoleWriter returning as LoggerInterface.
func NewConsole() LoggerInterface {
cw := &ConsoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
Level: LevelDebug,
}
return cw
}
// init console logger.
// jsonconfig like '{"level":LevelTrace}'.
func (c *ConsoleWriter) Init(jsonconfig string) error {
if len(jsonconfig) == 0 {
return nil
}
return json.Unmarshal([]byte(jsonconfig), c)
}
// write message in console.
func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
if level > c.Level {
return nil
}
if goos := runtime.GOOS; goos == "windows" {
c.lg.Println(msg)
return nil
}
c.lg.Println(colors[level](msg))
return nil
}
// implementing method. empty.
func (c *ConsoleWriter) Destroy() {
}
// implementing method. empty.
func (c *ConsoleWriter) Flush() {
}
func init() {
Register("console", NewConsole)
}

View File

@@ -1,76 +0,0 @@
package es
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"time"
"github.com/astaxie/beego/logs"
"github.com/belogik/goes"
)
func NewES() logs.LoggerInterface {
cw := &esLogger{
Level: logs.LevelDebug,
}
return cw
}
type esLogger struct {
*goes.Connection
DSN string `json:"dsn"`
Level int `json:"level"`
}
// {"dsn":"http://localhost:9200/","level":1}
func (el *esLogger) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), el)
if err != nil {
return err
}
if el.DSN == "" {
return errors.New("empty dsn")
} else if u, err := url.Parse(el.DSN); err != nil {
return err
} else if u.Path == "" {
return errors.New("missing prefix")
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
return err
} else {
conn := goes.NewConnection(host, port)
el.Connection = conn
}
return nil
}
func (el *esLogger) WriteMsg(msg string, level int) error {
if level > el.Level {
return nil
}
t := time.Now()
vals := make(map[string]interface{})
vals["@timestamp"] = t.Format(time.RFC3339)
vals["@msg"] = msg
d := goes.Document{
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
Type: "logs",
Fields: vals,
}
_, err := el.Index(d, nil)
return err
}
func (el *esLogger) Destroy() {
}
func (el *esLogger) Flush() {
}
func init() {
logs.Register("es", NewES)
}

View File

@@ -1,283 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// 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 logs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// FileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type FileLogWriter struct {
*log.Logger
mw *MuxWriter
// The opened file
Filename string `json:"filename"`
Maxlines int `json:"maxlines"`
maxlines_curlines int
// Rotate at size
Maxsize int `json:"maxsize"`
maxsize_cursize int
// Rotate daily
Daily bool `json:"daily"`
Maxdays int64 `json:"maxdays"`
daily_opendate int
Rotate bool `json:"rotate"`
startLock sync.Mutex // Only one log can write to the file
Level int `json:"level"`
}
// an *os.File writer with locker.
type MuxWriter struct {
sync.Mutex
fd *os.File
}
// write to os.File.
func (l *MuxWriter) Write(b []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.fd.Write(b)
}
// set os.File in writer.
func (l *MuxWriter) SetFd(fd *os.File) {
if l.fd != nil {
l.fd.Close()
}
l.fd = fd
}
// create a FileLogWriter returning as LoggerInterface.
func NewFileWriter() LoggerInterface {
w := &FileLogWriter{
Filename: "",
Maxlines: 1000000,
Maxsize: 1 << 28, //256 MB
Daily: true,
Maxdays: 7,
Rotate: true,
Level: LevelTrace,
}
// use MuxWriter instead direct use os.File for lock write when rotate
w.mw = new(MuxWriter)
// set MuxWriter as Logger's io.Writer
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
return w
}
// Init file logger with json config.
// jsonconfig like:
// {
// "filename":"logs/beego.log",
// "maxlines":10000,
// "maxsize":1<<30,
// "daily":true,
// "maxdays":15,
// "rotate":true
// }
func (w *FileLogWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), w)
if err != nil {
return err
}
if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename")
}
err = w.startLogger()
return err
}
// start file logger. create log file and set to locker-inside file writer.
func (w *FileLogWriter) startLogger() error {
fd, err := w.createLogFile()
if err != nil {
return err
}
w.mw.SetFd(fd)
return w.initFd()
}
func (w *FileLogWriter) docheck(size int) {
w.startLock.Lock()
defer w.startLock.Unlock()
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
(w.Daily && time.Now().Day() != w.daily_opendate)) {
if err := w.DoRotate(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
return
}
}
w.maxlines_curlines++
w.maxsize_cursize += size
}
// write logger message into file.
func (w *FileLogWriter) WriteMsg(msg string, level int) error {
if level > w.Level {
return nil
}
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
w.docheck(n)
w.Logger.Println(msg)
return nil
}
func (w *FileLogWriter) createLogFile() (*os.File, error) {
// Open the log file
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
return fd, err
}
func (w *FileLogWriter) initFd() error {
fd := w.mw.fd
finfo, err := fd.Stat()
if err != nil {
return fmt.Errorf("get stat err: %s\n", err)
}
w.maxsize_cursize = int(finfo.Size())
w.daily_opendate = time.Now().Day()
w.maxlines_curlines = 0
if finfo.Size() > 0 {
count, err := w.lines()
if err != nil {
return err
}
w.maxlines_curlines = count
}
return nil
}
func (w *FileLogWriter) lines() (int, error) {
fd, err := os.Open(w.Filename)
if err != nil {
return 0, err
}
defer fd.Close()
buf := make([]byte, 32768) // 32k
count := 0
lineSep := []byte{'\n'}
for {
c, err := fd.Read(buf)
if err != nil && err != io.EOF {
return count, err
}
count += bytes.Count(buf[:c], lineSep)
if err == io.EOF {
break
}
}
return count, nil
}
// DoRotate means it need to write file in new file.
// new file name like xx.log.2013-01-01.2
func (w *FileLogWriter) DoRotate() error {
_, err := os.Lstat(w.Filename)
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999; num++ {
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
_, err = os.Lstat(fname)
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
}
// block Logger's io.Writer
w.mw.Lock()
defer w.mw.Unlock()
fd := w.mw.fd
fd.Close()
// close fd before rename
// Rename the file to its newfound home
err = os.Rename(w.Filename, fname)
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
// re-start logger
err = w.startLogger()
if err != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", err)
}
go w.deleteOldLog()
}
return nil
}
func (w *FileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
fmt.Println(returnErr)
}
}()
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
os.Remove(path)
}
}
return
})
}
// destroy file logger, close file writer.
func (w *FileLogWriter) Destroy() {
w.mw.fd.Close()
}
// flush file logger.
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func (w *FileLogWriter) Flush() {
w.mw.fd.Sync()
}
func init() {
Register("file", NewFileWriter)
}

View File

@@ -1,350 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// 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.
// Usage:
//
// import "github.com/astaxie/beego/logs"
//
// log := NewLogger(10000)
// log.SetLogger("console", "")
//
// > the first params stand for how many channel
//
// Use it like this:
//
// log.Trace("trace")
// log.Info("info")
// log.Warn("warning")
// log.Debug("debug")
// log.Critical("critical")
//
// more docs http://beego.me/docs/module/logs.md
package logs
import (
"fmt"
"path"
"runtime"
"sync"
)
// RFC5424 log message levels.
const (
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
LevelWarning
LevelNotice
LevelInformational
LevelDebug
)
// Legacy loglevel constants to ensure backwards compatibility.
//
// Deprecated: will be removed in 1.5.0.
const (
LevelInfo = LevelInformational
LevelTrace = LevelDebug
LevelWarn = LevelWarning
)
type loggerType func() LoggerInterface
// LoggerInterface defines the behavior of a log provider.
type LoggerInterface interface {
Init(config string) error
WriteMsg(msg string, level int) error
Destroy()
Flush()
}
var adapters = make(map[string]loggerType)
// Register makes a log provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, log loggerType) {
if log == nil {
panic("logs: Register provide is nil")
}
if _, dup := adapters[name]; dup {
panic("logs: Register called twice for provider " + name)
}
adapters[name] = log
}
// BeeLogger is default logger in beego application.
// it can contain several providers and log message into all providers.
type BeeLogger struct {
lock sync.Mutex
level int
enableFuncCallDepth bool
loggerFuncCallDepth int
asynchronous bool
msg chan *logMsg
outputs map[string]LoggerInterface
}
type logMsg struct {
level int
msg string
}
// NewLogger returns a new BeeLogger.
// channellen means the number of messages in chan.
// if the buffering chan is full, logger adapters write to file or other way.
func NewLogger(channellen int64) *BeeLogger {
bl := new(BeeLogger)
bl.level = LevelDebug
bl.loggerFuncCallDepth = 2
bl.msg = make(chan *logMsg, channellen)
bl.outputs = make(map[string]LoggerInterface)
return bl
}
func (bl *BeeLogger) Async() *BeeLogger {
bl.asynchronous = true
go bl.startLogger()
return bl
}
// SetLogger provides a given logger adapter into BeeLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
if log, ok := adapters[adaptername]; ok {
lg := log()
err := lg.Init(config)
bl.outputs[adaptername] = lg
if err != nil {
fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
return err
}
} else {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
}
return nil
}
// remove a logger adapter in BeeLogger.
func (bl *BeeLogger) DelLogger(adaptername string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
if lg, ok := bl.outputs[adaptername]; ok {
lg.Destroy()
delete(bl.outputs, adaptername)
return nil
} else {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
}
}
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
lm := new(logMsg)
lm.level = loglevel
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if !ok {
file = "???"
line = 0
}
_, filename := path.Split(file)
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
} else {
lm.msg = msg
}
if bl.asynchronous {
bl.msg <- lm
} else {
for name, l := range bl.outputs {
err := l.WriteMsg(lm.msg, lm.level)
if err != nil {
fmt.Println("unable to WriteMsg to adapter:", name, err)
return err
}
}
}
return nil
}
// Set log message level.
//
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
// log providers will not even be sent the message.
func (bl *BeeLogger) SetLevel(l int) {
bl.level = l
}
// set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d
}
// get log funcCallDepth for wrapper
func (bl *BeeLogger) GetLogFuncCallDepth() int {
return bl.loggerFuncCallDepth
}
// enable log funcCallDepth
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b
}
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
for {
select {
case bm := <-bl.msg:
for _, l := range bl.outputs {
err := l.WriteMsg(bm.msg, bm.level)
if err != nil {
fmt.Println("ERROR, unable to WriteMsg:", err)
}
}
}
}
}
// Log EMERGENCY level message.
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
if LevelEmergency > bl.level {
return
}
msg := fmt.Sprintf("[M] "+format, v...)
bl.writerMsg(LevelEmergency, msg)
}
// Log ALERT level message.
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
if LevelAlert > bl.level {
return
}
msg := fmt.Sprintf("[A] "+format, v...)
bl.writerMsg(LevelAlert, msg)
}
// Log CRITICAL level message.
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
if LevelCritical > bl.level {
return
}
msg := fmt.Sprintf("[C] "+format, v...)
bl.writerMsg(LevelCritical, msg)
}
// Log ERROR level message.
func (bl *BeeLogger) Error(format string, v ...interface{}) {
if LevelError > bl.level {
return
}
msg := fmt.Sprintf("[E] "+format, v...)
bl.writerMsg(LevelError, msg)
}
// Log WARNING level message.
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
if LevelWarning > bl.level {
return
}
msg := fmt.Sprintf("[W] "+format, v...)
bl.writerMsg(LevelWarning, msg)
}
// Log NOTICE level message.
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
if LevelNotice > bl.level {
return
}
msg := fmt.Sprintf("[N] "+format, v...)
bl.writerMsg(LevelNotice, msg)
}
// Log INFORMATIONAL level message.
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
if LevelInformational > bl.level {
return
}
msg := fmt.Sprintf("[I] "+format, v...)
bl.writerMsg(LevelInformational, msg)
}
// Log DEBUG level message.
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
if LevelDebug > bl.level {
return
}
msg := fmt.Sprintf("[D] "+format, v...)
bl.writerMsg(LevelDebug, msg)
}
// Log WARN level message.
// compatibility alias for Warning()
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
if LevelWarning > bl.level {
return
}
msg := fmt.Sprintf("[W] "+format, v...)
bl.writerMsg(LevelWarning, msg)
}
// Log INFO level message.
// compatibility alias for Informational()
func (bl *BeeLogger) Info(format string, v ...interface{}) {
if LevelInformational > bl.level {
return
}
msg := fmt.Sprintf("[I] "+format, v...)
bl.writerMsg(LevelInformational, msg)
}
// Log TRACE level message.
// compatibility alias for Debug()
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
if LevelDebug > bl.level {
return
}
msg := fmt.Sprintf("[D] "+format, v...)
bl.writerMsg(LevelDebug, msg)
}
// flush all chan data.
func (bl *BeeLogger) Flush() {
for _, l := range bl.outputs {
l.Flush()
}
}
// close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() {
for {
if len(bl.msg) > 0 {
bm := <-bl.msg
for _, l := range bl.outputs {
err := l.WriteMsg(bm.msg, bm.level)
if err != nil {
fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err)
}
}
continue
}
break
}
for _, l := range bl.outputs {
l.Flush()
l.Destroy()
}
}

View File

@@ -1,31 +0,0 @@
# Travis CI (http://travis-ci.org/) is a continuous integration
# service for open source projects. This file configures it
# to run unit tests for docopt-go.
language: go
go:
- 1.4
- 1.5
- tip
matrix:
fast_finish: true
before_install:
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/cover
- go get github.com/golang/lint/golint
- go get github.com/mattn/goveralls
install:
- go get -d -v ./... && go build -v ./...
script:
- go vet -x ./...
- $HOME/gopath/bin/golint ./...
- go test -v ./...
- go test -covermode=count -coverprofile=profile.cov .
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci

View File

@@ -1,88 +0,0 @@
docopt-go
=========
[![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go)
[![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go)
[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go)
An implementation of [docopt](http://docopt.org/) in the
[Go](http://golang.org/) programming language.
**docopt** helps you create *beautiful* command-line interfaces easily:
```go
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Naval Fate.
Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> [--speed=<kn>]
naval_fate ship shoot <x> <y>
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
naval_fate -h | --help
naval_fate --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.`
arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
fmt.Println(arguments)
}
```
**docopt** parses command-line arguments based on a help message. Don't
write parser code: a good help message already has all the necessary
information in it.
## Installation
⚠ Use the alias “docopt-go”. To use docopt in your Go code:
```go
import "github.com/docopt/docopt-go"
```
To install docopt according to your `$GOPATH`:
```console
$ go get github.com/docopt/docopt-go
```
## API
```go
func Parse(doc string, argv []string, help bool, version string,
optionsFirst bool, exit ...bool) (map[string]interface{}, error)
```
Parse `argv` based on the command-line interface described in `doc`.
Given a conventional command-line help message, docopt creates a parser and
processes the arguments. See
https://github.com/docopt/docopt#help-message-format for a description of the
help message format. If `argv` is `nil`, `os.Args[1:]` is used.
docopt returns a map of option names to the values parsed from `argv`, and an
error or `nil`.
More documentation for docopt is available at
[GoDoc.org](https://godoc.org/github.com/docopt/docopt.go).
## Testing
All tests from the Python version are implemented and passing
at [Travis CI](https://travis-ci.org/docopt/docopt.go). New
language-agnostic tests have been added
to [test_golang.docopt](test_golang.docopt).
To run tests for docopt-go, use `go test`.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
r"""usage: prog [NAME_-2]..."""
$ prog 10 20
{"NAME_-2": ["10", "20"]}
$ prog 10
{"NAME_-2": ["10"]}
$ prog
{"NAME_-2": []}

View File

@@ -1,957 +0,0 @@
r"""Usage: prog
"""
$ prog
{}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -a All.
"""
$ prog
{"-a": false}
$ prog -a
{"-a": true}
$ prog -x
"user-error"
r"""Usage: prog [options]
Options: --all All.
"""
$ prog
{"--all": false}
$ prog --all
{"--all": true}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -v, --verbose Verbose.
"""
$ prog --verbose
{"--verbose": true}
$ prog --ver
{"--verbose": true}
$ prog -v
{"--verbose": true}
r"""Usage: prog [options]
Options: -p PATH
"""
$ prog -p home/
{"-p": "home/"}
$ prog -phome/
{"-p": "home/"}
$ prog -p
"user-error"
r"""Usage: prog [options]
Options: --path <path>
"""
$ prog --path home/
{"--path": "home/"}
$ prog --path=home/
{"--path": "home/"}
$ prog --pa home/
{"--path": "home/"}
$ prog --pa=home/
{"--path": "home/"}
$ prog --path
"user-error"
r"""Usage: prog [options]
Options: -p PATH, --path=<path> Path to files.
"""
$ prog -proot
{"--path": "root"}
r"""Usage: prog [options]
Options: -p --path PATH Path to files.
"""
$ prog -p root
{"--path": "root"}
$ prog --path root
{"--path": "root"}
r"""Usage: prog [options]
Options:
-p PATH Path to files [default: ./]
"""
$ prog
{"-p": "./"}
$ prog -phome
{"-p": "home"}
r"""UsAgE: prog [options]
OpTiOnS: --path=<files> Path to files
[dEfAuLt: /root]
"""
$ prog
{"--path": "/root"}
$ prog --path=home
{"--path": "home"}
r"""usage: prog [options]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
$ prog -a -r
{"-a": true,
"-r": true,
"-m": null}
r"""Usage: prog [options]
Options: --version
--verbose
"""
$ prog --version
{"--version": true,
"--verbose": false}
$ prog --verbose
{"--version": false,
"--verbose": true}
$ prog --ver
"user-error"
$ prog --verb
{"--version": false,
"--verbose": true}
r"""usage: prog [-a -r -m <msg>]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
r"""usage: prog [-armmsg]
options: -a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
r"""usage: prog -a -b
options:
-a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog (-a -b)
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog [-a] -b
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
{"-a": false, "-b": true}
$ prog
"user-error"
r"""usage: prog [(-a -b)]
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
"user-error"
$ prog
{"-a": false, "-b": false}
r"""usage: prog (-a|-b)
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
"user-error"
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog [ -a | -b ]
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
{"-a": false, "-b": false}
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog <arg>"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog [<arg>]"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
{"<arg>": null}
r"""usage: prog <kind> <name> <type>"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog <kind> [<name> <type>]"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
{"<kind>": "10", "<name>": "20", "<type>": null}
$ prog
"user-error"
r"""usage: prog [<kind> | <name> <type>]"""
$ prog 10 20 40
"user-error"
$ prog 20 40
{"<kind>": null, "<name>": "20", "<type>": "40"}
$ prog
{"<kind>": null, "<name>": null, "<type>": null}
r"""usage: prog (<kind> --all | <name>)
options:
--all
"""
$ prog 10 --all
{"<kind>": "10", "--all": true, "<name>": null}
$ prog 10
{"<kind>": null, "--all": false, "<name>": "10"}
$ prog
"user-error"
r"""usage: prog [<name> <name>]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
{"<name>": ["10"]}
$ prog
{"<name>": []}
r"""usage: prog [(<name> <name>)]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
"user-error"
$ prog
{"<name>": []}
r"""usage: prog NAME..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
"user-error"
r"""usage: prog [NAME]..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME...]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME [NAME ...]]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog (NAME | --foo NAME)
options: --foo
"""
$ prog 10
{"NAME": "10", "--foo": false}
$ prog --foo 10
{"NAME": "10", "--foo": true}
$ prog --foo=10
"user-error"
r"""usage: prog (NAME | --foo) [--bar | NAME]
options: --foo
options: --bar
"""
$ prog 10
{"NAME": ["10"], "--foo": false, "--bar": false}
$ prog 10 20
{"NAME": ["10", "20"], "--foo": false, "--bar": false}
$ prog --foo --bar
{"NAME": [], "--foo": true, "--bar": true}
r"""Naval Fate.
Usage:
prog ship new <name>...
prog ship [<name>] move <x> <y> [--speed=<kn>]
prog ship shoot <x> <y>
prog mine (set|remove) <x> <y> [--moored|--drifting]
prog -h | --help
prog --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Mored (anchored) mine.
--drifting Drifting mine.
"""
$ prog ship Guardian move 150 300 --speed=20
{"--drifting": false,
"--help": false,
"--moored": false,
"--speed": "20",
"--version": false,
"<name>": ["Guardian"],
"<x>": "150",
"<y>": "300",
"mine": false,
"move": true,
"new": false,
"remove": false,
"set": false,
"ship": true,
"shoot": false}
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: prog --aabb | --aa"""
$ prog --aa
{"--aabb": false, "--aa": true}
$ prog --a
"user-error" # not a unique prefix
#
# Counting number of flags
#
r"""Usage: prog -v"""
$ prog -v
{"-v": true}
r"""Usage: prog [-v -v]"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
r"""Usage: prog -v ..."""
$ prog
"user-error"
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvvvv
{"-v": 6}
r"""Usage: prog [-v | -vv | -vvv]
This one is probably most readable user-friednly variant.
"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvv
"user-error"
r"""usage: prog [--ver --ver]"""
$ prog --ver --ver
{"--ver": 2}
#
# Counting commands
#
r"""usage: prog [go]"""
$ prog go
{"go": true}
r"""usage: prog [go go]"""
$ prog
{"go": 0}
$ prog go
{"go": 1}
$ prog go go
{"go": 2}
$ prog go go go
"user-error"
r"""usage: prog go..."""
$ prog go go go go go
{"go": 5}
#
# [options] does not include options from usage-pattern
#
r"""usage: prog [options] [-a]
options: -a
-b
"""
$ prog -a
{"-a": true, "-b": false}
$ prog -aa
"user-error"
#
# Test [options] shourtcut
#
r"""Usage: prog [options] A
Options:
-q Be quiet
-v Be verbose.
"""
$ prog arg
{"A": "arg", "-v": false, "-q": false}
$ prog -v arg
{"A": "arg", "-v": true, "-q": false}
$ prog -q arg
{"A": "arg", "-v": false, "-q": true}
#
# Test single dash
#
r"""usage: prog [-]"""
$ prog -
{"-": true}
$ prog
{"-": false}
#
# If argument is repeated, its value should always be a list
#
r"""usage: prog [NAME [NAME ...]]"""
$ prog a b
{"NAME": ["a", "b"]}
$ prog
{"NAME": []}
#
# Option's argument defaults to null/None
#
r"""usage: prog [options]
options:
-a Add
-m <msg> Message
"""
$ prog -a
{"-m": null, "-a": true}
#
# Test options without description
#
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: git [-v | --verbose]"""
$ prog -v
{"-v": true, "--verbose": false}
r"""usage: git remote [-v | --verbose]"""
$ prog remote -v
{"remote": true, "-v": true, "--verbose": false}
#
# Test empty usage pattern
#
r"""usage: prog"""
$ prog
{}
r"""usage: prog
prog <a> <b>
"""
$ prog 1 2
{"<a>": "1", "<b>": "2"}
$ prog
{"<a>": null, "<b>": null}
r"""usage: prog <a> <b>
prog
"""
$ prog
{"<a>": null, "<b>": null}
#
# Option's argument should not capture default value from usage pattern
#
r"""usage: prog [--file=<f>]"""
$ prog
{"--file": null}
r"""usage: prog [--file=<f>]
options: --file <a>
"""
$ prog
{"--file": null}
r"""Usage: prog [-a <host:port>]
Options: -a, --address <host:port> TCP address [default: localhost:6283].
"""
$ prog
{"--address": "localhost:6283"}
#
# If option with argument could be repeated,
# its arguments should be accumulated into a list
#
r"""usage: prog --long=<arg> ..."""
$ prog --long one
{"--long": ["one"]}
$ prog --long one --long two
{"--long": ["one", "two"]}
#
# Test multiple elements repeated at once
#
r"""usage: prog (go <direction> --speed=<km/h>)..."""
$ prog go left --speed=5 go right --speed=9
{"go": 2, "<direction>": ["left", "right"], "--speed": ["5", "9"]}
#
# Required options should work with option shortcut
#
r"""usage: prog [options] -a
options: -a
"""
$ prog -a
{"-a": true}
#
# If option could be repeated its defaults should be split into a list
#
r"""usage: prog [-o <o>]...
options: -o <o> [default: x]
"""
$ prog -o this -o that
{"-o": ["this", "that"]}
$ prog
{"-o": ["x"]}
r"""usage: prog [-o <o>]...
options: -o <o> [default: x y]
"""
$ prog -o this
{"-o": ["this"]}
$ prog
{"-o": ["x", "y"]}
#
# Test stacked option's argument
#
r"""usage: prog -pPATH
options: -p PATH
"""
$ prog -pHOME
{"-p": "HOME"}
#
# Issue 56: Repeated mutually exclusive args give nested lists sometimes
#
r"""Usage: foo (--xx=x|--yy=y)..."""
$ prog --xx=1 --yy=2
{"--xx": ["1"], "--yy": ["2"]}
#
# POSIXly correct tokenization
#
r"""usage: prog [<input file>]"""
$ prog f.txt
{"<input file>": "f.txt"}
r"""usage: prog [--input=<file name>]..."""
$ prog --input a.txt --input=b.txt
{"--input": ["a.txt", "b.txt"]}
#
# Issue 85: `[options]` shourtcut with multiple subcommands
#
r"""usage: prog good [options]
prog fail [options]
options: --loglevel=N
"""
$ prog fail --loglevel 5
{"--loglevel": "5", "fail": true, "good": false}
#
# Usage-section syntax
#
r"""usage:prog --foo"""
$ prog --foo
{"--foo": true}
r"""PROGRAM USAGE: prog --foo"""
$ prog --foo
{"--foo": true}
r"""Usage: prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
#
# Options-section syntax
#
r"""Usage: prog [options]
global options: --foo
local options: --baz
--bar
other options:
--egg
--spam
-not-an-option-
"""
$ prog --baz --egg
{"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false}

View File

@@ -1,21 +1,43 @@
export PATH := $(GOPATH)/bin:$(PATH)
export NEW_GOPATH := $(shell pwd)
all: build
all: fmt build
build: godep fmt frps frpc
build: frps frpc
godep:
@go get github.com/tools/godep
# compile assets into binary file
file:
rm -rf ./assets/frps/static/*
rm -rf ./assets/frpc/static/*
cp -rf ./web/frps/dist/* ./assets/frps/static
cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/...
fmt:
GOPATH=$(NEW_GOPATH) godep go fmt ./...
go fmt ./...
frps:
GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps
go build -o bin/frps ./cmd/frps
frpc:
GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc
go build -o bin/frpc ./cmd/frpc
test:
@GOPATH=$(NEW_GOPATH) godep go test -v ./...
test: gotest
gotest:
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/...
ci:
go test -count=1 -p=1 -v ./tests/...
alltest: gotest ci
clean:
rm -f ./bin/frpc
rm -f ./bin/frps

37
Makefile.cross-compiles Normal file
View File

@@ -0,0 +1,37 @@
export PATH := $(GOPATH)/bin:$(PATH)
LDFLAGS := -s -w
all: build
build: app
app:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=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
temp:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps

840
README.md
View File

@@ -1,33 +1,845 @@
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
[![Build Status](https://travis-ci.org/fatedier/frp.svg?branch=master)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
## What is frp?
frp is a fast reverse proxy which can help you expose a local server behind a NAT or firewall to the internet.
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.
Now it also try to support p2p connect.
## Table of Contents
<!-- vim-markdown-toc GFM -->
* [Status](#status)
* [Architecture](#architecture)
* [Example Usage](#example-usage)
* [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)
* [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)
* [Configuration File](#configuration-file)
* [Configuration file template](#configuration-file-template)
* [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression)
* [TLS](#tls)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [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)
* [Load balancing](#load-balancing)
* [Health Check](#health-check)
* [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)
* [Custom subdomain names](#custom-subdomain-names)
* [URL routing](#url-routing)
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
* [Range ports mapping](#range-ports-mapping)
* [Plugin](#plugin)
* [Development Plan](#development-plan)
* [Contributing](#contributing)
* [Donation](#donation)
* [AliPay](#alipay)
* [Wechat Pay](#wechat-pay)
* [Paypal](#paypal)
<!-- vim-markdown-toc -->
## Status
frp is under development and you can try it with available version 0.1.
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
## Quick Start
Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md)
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
## Architecture
![architecture](doc/pic/architecture.png)
![architecture](/doc/pic/architecture.png)
## What can I do with frp?
## Example Usage
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address.
* 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).
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
Put **frps** and **frps.ini** to your server with public IP.
Put **frpc** and **frpc.ini** to your server in LAN.
### Access your computer in LAN by SSH
1. Modify frps.ini:
```ini
# frps.ini
[common]
bind_port = 7000
```
2. Start frps:
`./frps -c ./frps.ini`
3. Modify frpc.ini, `server_addr` is your frps's server IP:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
```
4. Start frpc:
`./frpc -c ./frpc.ini`
5. Connect to server in LAN by ssh assuming that username is test:
`ssh -oPort=6000 test@x.x.x.x`
### Visit your web service in LAN by custom domains
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.
However, we can expose a http or https service using frp.
1. Modify frps.ini, configure http port 8080:
```ini
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
```
2. Start frps:
`./frps -c ./frps.ini`
3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[web]
type = http
local_port = 80
custom_domains = www.yourdomain.com
```
4. Start frpc:
`./frpc -c ./frpc.ini`
5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
### Forward DNS query request
1. Modify frps.ini:
```ini
# frps.ini
[common]
bind_port = 7000
```
2. Start frps:
`./frps -c ./frps.ini`
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000
```
4. Start frpc:
`./frpc -c ./frpc.ini`
5. Send dns query request by dig:
`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
### 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
Check frp's status and proxies's statistics information by Dashboard.
Configure a port for dashboard to enable this feature:
```ini
[common]
dashboard_port = 7500
# dashboard's username and password are both optionalif not set, default is admin.
dashboard_user = admin
dashboard_pwd = admin
```
Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`.
![dashboard](/doc/pic/dashboard.png)
### 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
`token` in frps.ini and frpc.ini should be same.
### Encryption and Compression
Defalut value is false, you could decide if the proxy will use encryption or compression:
```ini
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true
```
#### TLS
frp support TLS protocol between frpc and frps since v0.25.0.
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
# frps.ini
[common]
bind_port = 7000
# kcp needs to bind a udp port, it can be same with 'bind_port'
kcp_bind_port = 7000
```
2. Configure the protocol used in frpc to connect frps:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
# specify the 'kcp_bind_port' in frps
server_port = 7000
protocol = kcp
```
### 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.
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:
```ini
# frps.ini
[common]
max_pool_count = 5
```
2. Enable and specify the number of connection pool:
```ini
# frpc.ini
[common]
pool_count = 1
```
### 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
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
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
```
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
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
It can only be enabled when proxy type is http.
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
http_user = abc
http_pwd = abc
```
Visit `http://test.yourdomain.com` and now you need to input username and password.
### Custom subdomain names
It is convenient to use `subdomain` configure for http、https type when many people use one frps server together.
```ini
# frps.ini
subdomain_host = frps.com
```
Resolve `*.frps.com` to the frps server's IP.
```ini
# frpc.ini
[web]
type = http
local_port = 80
subdomain = test
```
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`.
### 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
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
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
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
* Log http request information in frps.
## Contributing
Interested in getting involved? We would love to help you!
Interested in getting involved? We would like to help you!
For simple bug fixes, just submit a PR with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an issue.
* 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.
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
* If you have some wonderful ideas, send email to fatedier@gmail.com.
If you have some wanderful 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.**
## Donation
If frp help you a lot, you can support us by:
frp QQ group: 606194980
### AliPay
![donation-alipay](/doc/pic/donate-alipay.png)
### Wechat Pay
![donation-wechatpay](/doc/pic/donate-wechatpay.png)
### Paypal
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.

904
README_zh.md Normal file
View File

@@ -0,0 +1,904 @@
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg?branch=master)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。
## 目录
<!-- vim-markdown-toc GFM -->
* [开发状态](#开发状态)
* [架构](#架构)
* [使用示例](#使用示例)
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix 域套接字](#转发-unix-域套接字)
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
* [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透)
* [功能说明](#功能说明)
* [配置文件](#配置文件)
* [配置文件模版渲染](#配置文件模版渲染)
* [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [身份验证](#身份验证)
* [加密与压缩](#加密与压缩)
* [TLS](#tls)
* [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态)
* [端口白名单](#端口白名单)
* [端口复用](#端口复用)
* [TCP 多路复用](#tcp-多路复用)
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
* [连接池](#连接池)
* [负载均衡](#负载均衡)
* [健康检查](#健康检查)
* [修改 Host Header](#修改-host-header)
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
* [获取用户真实 IP](#获取用户真实-ip)
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
* [Proxy Protocol](#proxy-protocol)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由)
* [通过代理连接 frps](#通过代理连接-frps)
* [范围端口映射](#范围端口映射)
* [插件](#插件)
* [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献)
* [捐助](#捐助)
* [知识星球](#知识星球)
* [支付宝扫码捐赠](#支付宝扫码捐赠)
* [微信支付捐赠](#微信支付捐赠)
* [Paypal 捐赠](#paypal-捐赠)
<!-- vim-markdown-toc -->
## 开发状态
frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。
master 分支用于发布稳定版本dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
**目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。**
## 架构
![architecture](/doc/pic/architecture.png)
## 使用示例
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
**frps****frps.ini** 放到具有公网 IP 的机器上。
**frpc****frpc.ini** 放到处于内网环境的机器上。
### 通过 ssh 访问公司内网机器
1. 修改 frps.ini 文件,这里使用了最简化的配置:
```ini
# frps.ini
[common]
bind_port = 7000
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
```
4. 启动 frpc
`./frpc -c ./frpc.ini`
5. 通过 ssh 访问内网机器,假设用户名为 test
`ssh -oPort=6000 test@x.x.x.x`
### 通过自定义域名访问部署于内网的 web 服务
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP无法将域名解析到本地的机器通过 frp 就可以实现这一功能,以下示例为 http 服务https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port type 设置为 https 即可。
1. 修改 frps.ini 文件,设置 http 访问端口为 8080
```ini
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frpc.ini 文件,假设 frps 所在的服务器的 IP 为 x.x.x.xlocal_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[web]
type = http
local_port = 80
custom_domains = www.yourdomain.com
```
4. 启动 frpc
`./frpc -c ./frpc.ini`
5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。
### 转发 DNS 查询请求
DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
1. 修改 frps.ini 文件:
```ini
# frps.ini
[common]
bind_port = 7000
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x转发到 Google 的 DNS 查询服务器 `8.8.8.8` 的 udp 53 端口:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000
```
4. 启动 frpc
`./frpc -c ./frpc.ini`
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
`dig @x.x.x.x -p 6000 www.google.com`
### 转发 Unix 域套接字
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
frps 的部署步骤同上。
1. 启动 frpc启用 `unix_domain_socket` 插件,配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[unix_domain_socket]
type = tcp
remote_port = 6000
plugin = unix_domain_socket
plugin_unix_path = /var/run/docker.sock
```
2. 通过 curl 命令查看 docker 版本信息
`curl http://x.x.x.x:6000/version`
### 对外提供简单的文件访问服务
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
frps 的部署步骤同上。
1. 启动 frpc启用 `static_file` 插件,配置如下:
```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
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
```
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
### 为本地 HTTP 服务启用 HTTPS
通过 `https2http` 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
1. 启用 frpc启用 `https2http` 插件,配置如下:
```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
# HTTPS 证书相关的配置
plugin_crt_path = ./server.crt
plugin_key_path = ./server.key
plugin_host_header_rewrite = 127.0.0.1
```
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
### 安全地暴露内网服务
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
使用 **stcp(secret tcp)** 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc。
以下示例将会创建一个只有自己能访问到的 ssh 服务代理。
frps 的部署步骤同上。
1. 启动 frpc转发内网的 ssh 服务,配置如下,不需要指定远程端口:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[secret_ssh]
type = stcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
2. 在要访问这个服务的机器上启动另外一个 frpc配置如下
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_visitor]
type = stcp
# stcp 的访问者
role = visitor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
sk = abcdefg
# 绑定本地端口用于访问 ssh 服务
bind_addr = 127.0.0.1
bind_port = 6000
```
3. 通过 ssh 访问内网机器,假设用户名为 test
`ssh -oPort=6000 test@127.0.0.1`
### 点对点内网穿透
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
```ini
bind_udp_port = 7001
```
2. 启动 frpc转发内网的 ssh 服务,配置如下,不需要指定远程端口:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh]
type = xtcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
3. 在要访问这个服务的机器上启动另外一个 frpc配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh_visitor]
type = xtcp
# xtcp 的访问者
role = visitor
# 要访问的 xtcp 代理的名字
server_name = p2p_ssh
sk = abcdefg
# 绑定本地端口用于访问 ssh 服务
bind_addr = 127.0.0.1
bind_port = 6000
```
4. 通过 ssh 访问内网机器,假设用户名为 test:
`ssh -oPort=6000 test@127.0.0.1`
## 功能说明
### 配置文件
由于 frp 目前支持的功能和配置项较多,未在文档中列出的功能可以从完整的示例配置文件中发现。
[frps 完整配置文件](./conf/frps_full.ini)
[frpc 完整配置文件](./conf/frpc_full.ini)
### 配置文件模版渲染
配置文件支持使用系统环境变量进行模版渲染,模版格式采用 Go 的标准格式。
示例配置如下:
```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 }}
```
启动 frpc 程序:
```
export FRP_SERVER_ADDR="x.x.x.x"
export FRP_SSH_REMOTE_PORT="6000"
./frpc -c ./frpc.ini
```
frpc 会自动使用环境变量渲染配置文件模版,所有环境变量需要以 `.Envs` 为前缀。
### Dashboard
通过浏览器查看 frp 的状态以及代理统计信息展示。
**注Dashboard 尚未针对大量的 proxy 数据展示做优化,如果出现 Dashboard 访问较慢的情况,请不要启用此功能。**
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
```ini
[common]
dashboard_port = 7500
# dashboard 用户名密码,默认都为 admin
dashboard_user = admin
dashboard_pwd = admin
```
打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面,用户名密码默认为 `admin`。
![dashboard](/doc/pic/dashboard.png)
### Admin UI
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
```ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
```
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI用户名密码默认为 `admin`。
如果想要在外网环境访问 Admin UI将 7400 端口映射出去即可,但需要重视安全风险。
### 身份验证
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
### 加密与压缩
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy
```ini
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true
```
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
#### TLS
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
为了端口复用frp 建立 TLS 连接的第一个字节为 0x17。
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
### 客户端热加载配置文件
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
```ini
# frpc.ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
```
之后执行重启命令:
`frpc reload -c ./frpc.ini`
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
### 客户端查看代理状态
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
### 端口白名单
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
```ini
# frps.ini
[common]
allow_ports = 2000-3000,3001,3003,4000-50000
```
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### 端口复用
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口frps 会对连接的协议进行分析,之后进行不同的处理。
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
### TCP 多路复用
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
```ini
# frps.ini 和 frpc.ini 中
[common]
tcp_mux = false
```
### 底层通信可选 kcp 协议
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
开启 kcp 协议支持:
1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求:
```ini
# frps.ini
[common]
bind_port = 7000
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
kcp_bind_port = 7000
```
2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
# server_port 指定为 frps 的 kcp_bind_port
server_port = 7000
protocol = kcp
```
3. 像之前一样使用 frp需要注意开放相关机器上的 udp 的端口的访问权限。
### 连接池
默认情况下当用户请求建立连接后frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
这一功能比较适合有大量短连接请求时开启。
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值:
```ini
# frps.ini
[common]
max_pool_count = 5
```
2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量:
```ini
# frpc.ini
[common]
pool_count = 1
```
### 负载均衡
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
目前只支持 TCP 和 HTTP 类型的 proxy。
```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
```
用户连接 frps 服务器的 80 端口frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
TCP 类型代理要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
HTTP 类型代理要求 `group_key, custom_domains 或 subdomain 和 locations` 相同。
### 健康检查
通过给 proxy 加上健康检查的功能,可以在要反向代理的服务出现故障时,将这个服务从 frps 中摘除,搭配负载均衡的功能,可以用来实现高可用的架构,避免服务单点故障。
在每一个 proxy 的配置下加上 `health_check_type = {type}` 来启用健康检查功能。
**type** 目前可选 tcp 和 http。
tcp 只要能够建立连接则认为服务正常http 会发送一个 http 请求,服务需要返回 2xx 的状态码才会被认为正常。
tcp 示例配置如下:
```ini
# frpc.ini
[test1]
type = tcp
local_port = 22
remote_port = 6000
# 启用健康检查,类型为 tcp
health_check_type = tcp
# 建立连接超时时间为 3 秒
health_check_timeout_s = 3
# 连续 3 次检查失败,此 proxy 会被摘除
health_check_max_failed = 3
# 每隔 10 秒进行一次健康检查
health_check_interval_s = 10
```
http 示例配置如下:
```ini
# frpc.ini
[web]
type = http
local_ip = 127.0.0.1
local_port = 80
custom_domains = test.yourdomain.com
# 启用健康检查,类型为 http
health_check_type = http
# 健康检查发送 http 请求的 url后端服务需要返回 2xx 的 http 状态码
health_check_url = /status
health_check_interval_s = 10
health_check_max_failed = 3
health_check_timeout_s = 3
```
### 修改 Host Header
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
```
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
### 设置 HTTP 请求的 header
对于 `type = http` 的代理,可以设置在转发中动态添加的 header 参数。
```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
```
对于参数配置中所有以 `header_` 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 `X-From-Where: frp`。
### 获取用户真实 IP
#### HTTP X-Forwarded-For
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP默认启用。
#### Proxy Protocol
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
**Proxy Protocol** 功能启用后frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
这里以 https 类型为例:
```ini
# frpc.ini
[web]
type = https
local_port = 443
custom_domains = test.yourdomain.com
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
proxy_protocol_version = v2
```
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
### 通过密码保护你的 web 服务
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。
该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
http_user = abc
http_pwd = abc
```
通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
### 自定义二级域名
在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。
通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。
只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
```ini
# frps.ini
[common]
subdomain_host = frps.com
```
将泛域名 `*.frps.com` 解析到 frps 所在服务器的 IP 地址。
```ini
# frpc.ini
[web]
type = http
local_port = 80
subdomain = test
```
frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
**注:如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。**
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
### URL 路由
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。
```ini
# frpc.ini
[web01]
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
type = http
local_port = 81
custom_domains = web.yourdomain.com
locations = /news,/about
```
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02其余的请求会被转发到 web01。
### 通过代理连接 frps
在只能通过代理访问外网的环境内frpc 支持通过 HTTP PROXY 和 frps 进行通信。
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
仅在 `protocol = tcp` 时生效。
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080
```
### 范围端口映射
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy每一个 proxy 以数字为后缀命名。
例如要映射本地 6000-6005, 6007 这6个端口主要配置如下
```ini
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
```
实际连接成功后会创建 8 个 proxy命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。
### 插件
默认情况下frpc 只会转发请求到本地 tcp 或 udp 端口。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
使用 **http_proxy** 插件的示例:
```ini
# frpc.ini
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
```
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
## 开发计划
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
* frps 记录 http 请求日志。
## 为 frp 做贡献
frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。
* 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。
* Bug 的修复可以直接提交 Pull Request 到 dev 分支。
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
* 欢迎对说明文档做出改善,帮助更多的人使用 frp特别是英文文档。
* 贡献代码请提交 PR 至 dev 分支master 分支仅用于发布稳定可用版本。
* 如果你有任何其他方面的问题,欢迎反馈至 fatedier@gmail.com 共同交流。
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
## 捐助
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
frp 交流群606194980 (QQ 群号)
### 知识星球
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
![zsxq](/doc/pic/zsxq.jpg)
### 支付宝扫码捐赠
![donate-alipay](/doc/pic/donate-alipay.png)
### 微信支付捐赠
![donate-wechatpay](/doc/pic/donate-wechatpay.png)
### Paypal 捐赠
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。

75
assets/assets.go Normal file
View File

@@ -0,0 +1,75 @@
// 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 assets
//go:generate statik -src=./frps/static -dest=./frps
//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 (
"io/ioutil"
"net/http"
"os"
"path"
"github.com/rakyll/statik/fs"
)
var (
// store static files in memory by statik
FileSystem http.FileSystem
// if prefix is not empty, we get file content from disk
prefixPath string
)
// if path is empty, load assets in memory
// or set FileSystem using disk files
func Load(path string) (err error) {
prefixPath = path
if prefixPath != "" {
FileSystem = http.Dir(prefixPath)
return nil
} else {
FileSystem, err = fs.New()
}
return err
}
func ReadFile(file string) (content string, err error) {
if prefixPath == "" {
file, err := FileSystem.Open(path.Join("/", file))
if err != nil {
return content, err
}
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
} else {
file, err := os.Open(path.Join(prefixPath, file))
if err != nil {
return content, err
}
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
}
return content, err
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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>

View 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}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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>

View 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}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

72
client/admin.go Normal file
View 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
View 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
View 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
View 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
View 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 {
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
err := monitor.doCheck(ctx)
// check if this monitor has been closed
select {
case <-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
View 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")
}
}

View 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)
}
}

View 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
}

240
client/service.go Normal file
View File

@@ -0,0 +1,240 @@
// 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 {
conn.Close()
session.Close()
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 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
View 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
View 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()
}
}

View File

@@ -12,26 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package consts
package main
// server status
const (
Idle = iota
Working
import (
_ "github.com/fatedier/frp/assets/frpc/statik"
"github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto"
)
// connection type
const (
CtlConn = iota
WorkConn
)
func main() {
crypto.DefaultSalt = "frp"
// msg from client to server
const (
CSHeartBeatReq = 1
)
// msg from server to client
const (
SCHeartBeatRes = 100
)
sub.Execute()
}

96
cmd/frpc/sub/http.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View File

@@ -1,14 +1,9 @@
# [common] is integral section
[common]
server_addr = 0.0.0.0
server_addr = 127.0.0.1
server_port = 7000
# console or real logFile path like ./frpc.log
log_file = console
# debug, info, warn, error
log_level = debug
# test1 is the proxy name same as server's configuration
[test1]
passwd = 123
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

243
conf/frpc_full.ini Normal file
View 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

View File

@@ -1,14 +1,2 @@
# [common] is integral section
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# console or real logFile path like ./frps.log
log_file = console
# debug, info, warn, error
log_level = debug
# test1 is the proxy name, client will use this name and passwd to connect to server
[test1]
passwd = 123
bind_addr = 0.0.0.0
listen_port = 6000

70
conf/frps_full.ini Normal file
View 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

14
conf/systemd/frpc.service Normal file
View 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

View 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
View 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

View 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

BIN
doc/pic/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
doc/pic/donate-alipay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
doc/pic/zsxq.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,68 +0,0 @@
# Quick Start
frp is easier to use compared with other similar projects.
We will use a simple demo to demonstrate 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).
### 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`.
### 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).
### 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
# test is the custom name of proxy and there can be many proxies with unique name in one configure file
[test]
passwd = 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
# test is proxy name same with configure in frps.ini
[test]
passwd = 123
# local port which need to be transferred
local_port = 22
```

View File

@@ -1,66 +0,0 @@
# frp 使用文档
frp 相比于其他项目而言非常易于部署和使用这里我们用一个简单的示例演示如何通过一台拥有公网IP地址的服务器B访问处于内网环境中的服务器A的ssh端口服务器B的IP地址为 x.x.x.x测试时替换为真实的IP地址
### 下载源码
推荐直接使用 `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` 拷贝到相应目录下。
### 编译
进入下载后的源码根目录,执行 `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上存在的真实用户
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# 用于接收 frpc 连接的端口
bind_port = 7000
log_file = ./frps.log
log_level = info
# test 为代理的自定义名称可以有多个不能重复和frpc中名称对应
[test]
passwd = 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
# test需要和 frps.ini 中配置一致
[test]
passwd = 123
# 需要转发的本地端口
local_port = 22
```

32
g/g.go Normal file
View 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
}

34
go.mod Normal file
View File

@@ -0,0 +1,34 @@
module github.com/fatedier/frp
go 1.12
require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/davecgh/go-spew v1.1.0 // indirect
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.20190317085623-2063a803e6fe+incompatible
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
github.com/gorilla/websocket v1.2.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/pmezard/go-difflib v1.0.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.2.1
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
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
)

52
go.sum Normal file
View File

@@ -0,0 +1,52 @@
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.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+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/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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=
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab h1:w4c/LoOA2vE8SYwh8wEEQVRUwpph7TtcjH7AtZvOjy0=
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 h1:1FDlG4HI84rVePw1/0E/crL5tt2N+1blLJpY6UZ6krs=
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View 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
View 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
}

View 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
View 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
View 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
}

32
models/consts/consts.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 consts
var (
// proxy status
Idle string = "idle"
Working string = "working"
Closed string = "closed"
Online string = "online"
Offline string = "offline"
// proxy type
TcpProxy string = "tcp"
UdpProxy string = "udp"
HttpProxy string = "http"
HttpsProxy string = "https"
StcpProxy string = "stcp"
XtcpProxy string = "xtcp"
)

View File

@@ -12,23 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
package errors
type GeneralRes struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
import (
"errors"
)
type ClientCtlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name"`
Passwd string `json:"passwd"`
}
type ClientCtlRes struct {
GeneralRes
}
type ServerCtlReq struct {
Type int64 `json:"type"`
}
var (
ErrMsgType = errors.New("message type error")
ErrCtlClosed = errors.New("control is closed")
)

46
models/msg/ctl.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
}

View 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
View 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)
}
}
}()
}

View 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)
}

59
package.sh Executable file
View File

@@ -0,0 +1,59 @@
# compile for version
make
if [ $? -ne 0 ]; then
echo "make error"
exit 1
fi
frp_version=`./bin/frps --version`
echo "build version: $frp_version"
# cross_compiles
make -f ./Makefile.cross-compiles
rm -rf ./packages
mkdir ./packages
os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
for os in $os_all; do
for arch in $arch_all; do
frp_dir_name="frp_${frp_version}_${os}_${arch}"
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
if [ "x${os}" = x"windows" ]; then
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
continue
fi
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
continue
fi
mkdir ${frp_path}
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
else
if [ ! -f "./frpc_${os}_${arch}" ]; then
continue
fi
if [ ! -f "./frps_${os}_${arch}" ]; then
continue
fi
mkdir ${frp_path}
mv ./frpc_${os}_${arch} ${frp_path}/frpc
mv ./frps_${os}_${arch} ${frp_path}/frps
fi
cp ./LICENSE ${frp_path}
cp -rf ./conf/* ${frp_path}
# packages
cd ./packages
if [ "x${os}" = x"windows" ]; then
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
else
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
fi
cd ..
rm -rf ${frp_path}
done
done

494
server/control.go Normal file
View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
}

Some files were not shown because too many files have changed in this diff Show More