Compare commits

..

379 Commits

Author SHA1 Message Date
fatedier
e62d9a5242 Merge pull request #1415 from fatedier/dev
bump version to v0.29.0
2019-08-29 21:22:30 +08:00
fatedier
94212ac8b8 Merge pull request #1414 from fatedier/new
let max_pool_count valid
2019-08-29 21:17:30 +08:00
fatedier
e9e86fccf0 let max_pool_count valid 2019-08-29 21:13:21 +08:00
fatedier
58745992ef typo 2019-08-27 14:10:53 +08:00
fatedier
234d634bfe Merge pull request #1411 from fatedier/new
proxy protocol: fix detect method for IPV4 and IPV6, fix #1410
2019-08-26 15:30:26 +08:00
fatedier
fdc6902a90 proxy protocol: fix detect method for IPV4 and IPV6 2019-08-26 11:13:33 +08:00
fatedier
d8d587fd93 Merge pull request #1408 from velovix/issue-1387_api-documentation
Add documentation for common configuration fields, common proxy configuration, and sessions
2019-08-25 21:15:32 +08:00
Tyler Compton
92791260a7 Add documentation for base proxy config 2019-08-24 15:25:52 -07:00
Tyler Compton
4dfd851c46 Add docs for common config fields & sessions
Now that the common configuration objects and session objects are part
of a public API, they need to be documented in a way that can be read
with godoc. This commit should lead to easier development with FRP as a
library.
2019-08-24 15:20:34 -07:00
fatedier
bc4df74b5e Merge pull request #1401 from velovix/issue-1387_client-conf-as-argument
Pass client configuration as an argument
2019-08-22 00:14:56 +08:00
Tyler Compton
666f122a72 Pass client configuration as an argument
The ClientCommonConf, configuration file path, and server UDP port are
now passed around as arguments instead of being shared between
components as global variables. This allows for multiple clients to
exist in the same process, and allows client.Session to be used as a
library more easily.
2019-08-20 14:08:01 -07:00
fatedier
f999c8a87e Merge pull request #1396 from velovix/issue-1387_server-conf-as-argument
Pass server configuration as an argument
2019-08-21 00:30:01 +08:00
fatedier
90a32ab75d Merge pull request #1399 from renmu123/dev
fix one punctuation in Chinese docs
2019-08-20 17:24:05 +08:00
renmu123
0713fd28da fix one punctuation in Chinese docs 2019-08-20 17:17:17 +08:00
fatedier
f5b33e6de8 Merge pull request #1397 from velovix/issue-1387_client-assets-dir
Add an "assets_dir" option for frpc
2019-08-20 11:06:12 +08:00
fatedier
fc6043bb4d Merge pull request #1395 from velovix/issue-1387_load-assets-on-demand
Load assets for dashboard/admin panel on demand
2019-08-20 11:03:05 +08:00
Tyler Compton
bc46e3330a Add an "assets_dir" option for frpc
This option allows users to specify where they want assets to be loaded
from, like the "assets_dir" option that already exists for frps. This
allows library users to use the admin panel without having to bundle
assets with statik.
2019-08-19 16:51:03 -07:00
Tyler Compton
5fc7b3ceb5 Remove global ServerService variable
This variable didn't seem to be used anyway, so no further changes were
required.
2019-08-19 16:23:33 -07:00
Tyler Compton
6277af4790 Pass server configuration as an argument
The ServerCommonConf is now passed around as an argument instead of
being shared between components as a global variable. This allows for
more natural interaction with server.Session as a library and allows for
multiple servers to co-exist within the same process.

Related: #1387
2019-08-19 15:52:08 -07:00
Tyler Compton
00bd0a8af4 Load assets for dashboard/admin panel on demand
The client and server services now only attempt to load assets if the
dashboard or admin panel are enabled. This change makes it possible to
use FRP as a library without having to manage assets. If a library user
wants to start a server with the dashboard enabled, they will need to
set the DashboardPort and AssetsDir fields of ServerCommonConf.
2019-08-19 10:10:50 -07:00
fatedier
a415573e45 Merge pull request #1390 from fatedier/new
support disable_log_color for console mode
2019-08-15 23:50:17 +08:00
fatedier
e68012858e ci: add http load balancing test cases 2019-08-15 23:36:05 +08:00
fatedier
ca8a5b753c Merge pull request #1382 from Hurricanezwf/feature/add-httpstohttp-header
add support for customized http header when using https2http plugin
2019-08-14 21:00:16 +08:00
zhouwenfeng
d1f4ac0f2d add README for this feature 2019-08-14 20:44:44 +08:00
zhouwenfeng
ff357882ac use Set() instead of Add() when attaching additional headers 2019-08-14 20:36:10 +08:00
zhouwenfeng
934ac2b836 add support for add http header when using https2http plugin 2019-08-13 21:14:06 +08:00
fatedier
1ad50d5982 bump version to v0.29.0 2019-08-12 00:48:50 +08:00
fatedier
388b016842 support disable_log_color for console mode 2019-08-12 00:47:35 +08:00
fatedier
134a46c00b Merge pull request #1369 from fatedier/dev
bump version to v0.28.2
2019-08-09 12:59:13 +08:00
fatedier
50796643fb Merge pull request #1368 from fatedier/new
fix health check bug, fix #1367
2019-08-09 12:52:46 +08:00
fatedier
b1838b1d5e bump version to v0.28.2 2019-08-09 12:50:33 +08:00
fatedier
757b3613fe fix health check bug, fix #1367 2019-08-09 12:47:27 +08:00
fatedier
ae08811636 Merge pull request #1364 from fatedier/dev
bump version to v0.28.1 and remove support for go1.11
2019-08-08 17:32:57 +08:00
fatedier
b657c0fe09 Merge pull request #1358 from fatedier/new
update vendor packages
2019-08-06 18:59:03 +08:00
fatedier
84df71047c no support for go1.11 2019-08-06 18:53:32 +08:00
fatedier
abc6d720d0 vendor update github.com/gorilla/websocket 2019-08-06 18:53:15 +08:00
fatedier
80154639e3 fix 2019-08-06 17:29:35 +08:00
fatedier
f2117d8331 bump version to v0.28.1 2019-08-06 16:51:55 +08:00
fatedier
261be6a7b7 add vendor files 2019-08-06 16:50:54 +08:00
fatedier
b53a2c1ed9 update reverseproxy from std libraries 2019-08-06 16:49:22 +08:00
fatedier
ee0df07a3c vendor update 2019-08-03 23:23:00 +08:00
fatedier
4e363eca2b update version of github.com/gorilla/mux 2019-08-03 23:22:22 +08:00
fatedier
4277405c0e update vendors 2019-08-03 18:49:55 +08:00
fatedier
6a99f0caf7 update testify and kcp-go 2019-08-03 18:44:11 +08:00
fatedier
394af08561 close session in login() 2019-08-03 16:43:21 +08:00
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
996 changed files with 342070 additions and 14533 deletions

View File

@@ -1,5 +0,0 @@
Dockerfile
.git
*~
*#
.#*

View File

@@ -1,5 +1,7 @@
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

View File

@@ -2,7 +2,7 @@ sudo: false
language: go
go:
- 1.8.x
- 1.12.x
install:
- make

View File

@@ -1,17 +0,0 @@
FROM golang:1.8
COPY . /go/src/github.com/fatedier/frp
RUN cd /go/src/github.com/fatedier/frp \
&& make \
&& mv bin/frpc /frpc \
&& mv bin/frps /frps \
&& mv conf/frpc.ini /frpc.ini \
&& mv conf/frps.ini /frps.ini \
&& make clean
WORKDIR /
EXPOSE 80 443 6000 7000 7500
ENTRYPOINT ["/frps"]

View File

@@ -1,12 +0,0 @@
FROM alpine:3.5
COPY tmp/frpc /frpc
COPY tmp/frps /frps
COPY conf/frpc_min.ini /frpc.ini
COPY conf/frps_min.ini /frps.ini
WORKDIR /
EXPOSE 80 443 6000 7000 7500
ENTRYPOINT ["/frps"]

134
Godeps/Godeps.json generated
View File

@@ -1,134 +0,0 @@
{
"ImportPath": "github.com/fatedier/frp",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/armon/go-socks5",
"Rev": "e75332964ef517daa070d7c38a9466a0d687e0a5"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
},
{
"ImportPath": "github.com/docopt/docopt-go",
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/fatedier/beego/logs",
"Comment": "v1.7.2-72-gf73c369",
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
},
{
"ImportPath": "github.com/julienschmidt/httprouter",
"Comment": "v1.1-41-g8a45e95",
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
},
{
"ImportPath": "github.com/klauspost/cpuid",
"Comment": "v1.0",
"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
},
{
"ImportPath": "github.com/klauspost/reedsolomon",
"Comment": "1.3-1-gdde6ad5",
"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
},
{
"ImportPath": "github.com/pkg/errors",
"Comment": "v0.8.0-5-gc605e28",
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Comment": "v1.0.0",
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
},
{
"ImportPath": "github.com/rakyll/statik/fs",
"Comment": "v0.1.0",
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Comment": "v1.1.4-25-g2402e8e",
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "github.com/xtaci/kcp-go",
"Comment": "v3.17",
"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
},
{
"ImportPath": "github.com/xtaci/smux",
"Comment": "v1.0.5-8-g2de5471",
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/cast5",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/salsa20",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/tea",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/twofish",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/xtea",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/net/bpf",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/internal/socket",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/ipv4",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
}
]
}

5
Godeps/Readme generated
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.

View File

@@ -1,5 +1,4 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
all: fmt build
@@ -7,24 +6,19 @@ build: frps frpc
# compile assets into binary file
file:
rm -rf ./assets/static/*
cp -rf ./web/frps/dist/* ./assets/static
go get -d github.com/rakyll/statik
go install github.com/rakyll/statik
rm -rf ./assets/statik
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:
go fmt ./assets/...
go fmt ./client/...
go fmt ./cmd/...
go fmt ./models/...
go fmt ./server/...
go fmt ./utils/...
go fmt ./...
frps:
go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc:
go build -o bin/frpc ./cmd/frpc
@@ -32,22 +26,18 @@ frpc:
test: gotest
gotest:
go test -v ./assets/...
go test -v ./client/...
go test -v ./cmd/...
go test -v ./models/...
go test -v ./server/...
go test -v ./utils/...
go test -v --cover ./assets/...
go test -v --cover ./client/...
go test -v --cover ./cmd/...
go test -v --cover ./models/...
go test -v --cover ./server/...
go test -v --cover ./utils/...
alltest: gotest
cd ./tests && ./run_test.sh && cd -
go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd -
ci:
go test -count=1 -p=1 -v ./tests/...
alltest: gotest ci
clean:
rm -f ./bin/frpc
rm -f ./bin/frps
cd ./tests && ./clean_test.sh && cd -
save:
godep save ./...

View File

@@ -1,5 +1,4 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
LDFLAGS := -s -w
all: build
@@ -9,12 +8,18 @@ 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
@@ -23,10 +28,10 @@ app:
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
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

360
README.md
View File

@@ -6,12 +6,14 @@
## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, udp, http and https protocol when requests can be forwarded by domains to backward web services.
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
Now it also try to support p2p connect.
## Table of Contents
<!-- vim-markdown-toc GFM -->
* [What can I do with frp?](#what-can-i-do-with-frp)
* [Status](#status)
* [Architecture](#architecture)
* [Example Usage](#example-usage)
@@ -19,25 +21,37 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [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)
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
* [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)
* [Privilege Mode](#privilege-mode)
* [Port White List](#port-white-list)
* [Get proxy status from client](#get-proxy-status-from-client)
* [Port White List](#port-white-list)
* [Port Reuse](#port-reuse)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Support KCP Protocol](#support-kcp-protocol)
* [Connection Pool](#connection-pool)
* [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)
@@ -48,16 +62,11 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
<!-- vim-markdown-toc -->
## What can I do with frp?
* Expose any http and https service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
* Expose any tcp or udp service behind a NAT or firewall to the internet by a server with public IP address.
## Status
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
## Architecture
@@ -184,7 +193,7 @@ However, we can expose a http or https service using frp.
5. Send dns query request by dig:
`dig @x.x.x.x -p 6000 www.goolge.com`
`dig @x.x.x.x -p 6000 www.google.com`
### Forward unix domain socket
@@ -211,11 +220,61 @@ Configure frps same as above.
`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
plugin_header_X-From-Where = frp
```
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)** help you create a proxy avoiding any one can access it.
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
Configure frps same as above.
@@ -242,9 +301,9 @@ Configure frps same as above.
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
[secret_ssh_visitor]
type = stcp
role = vistor
role = visitor
server_name = secret_ssh
sk = abcdefg
bind_addr = 127.0.0.1
@@ -255,11 +314,19 @@ Configure frps same as above.
`ssh -oPort=6000 test@127.0.0.1`
### Connect website through frpc's network
### P2P Mode
Configure frps same as above.
**xtcp** is designed for transmitting a large amount of data directly between two client.
1. Start frpc with configurations:
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
@@ -267,13 +334,33 @@ Configure frps same as above.
server_addr = x.x.x.x
server_port = 7000
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy # or socks5
[p2p_ssh]
type = xtcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
2. Set http proxy or socks5 proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
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
@@ -285,6 +372,34 @@ You can find features which this document not metioned from full example configu
[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.
@@ -303,13 +418,25 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
![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
Since v0.10.0, you only need to set `privilege_token` in frps.ini and frpc.ini.
Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
`token` in frps.ini and frpc.ini should be same.
### Encryption and Compression
@@ -325,6 +452,14 @@ 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.
@@ -336,25 +471,31 @@ admin_addr = 127.0.0.1
admin_port = 7400
```
Then run command `frpc -c ./frpc.ini --reload` and wait for about 10 seconds to let frpc create or update or delete proxies.
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.**
### Privilege Mode
### Get proxy status from client
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in 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
### Port White List
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
`allow_ports` in frps.ini is used for preventing abuse of ports:
```ini
# frps.ini
[common]
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
allow_ports = 2000-3000,3001,3003,4000-50000
```
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
`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
@@ -370,8 +511,6 @@ tcp_mux = false
### Support KCP Protocol
frp support kcp protocol since v0.12.0.
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:
@@ -419,9 +558,86 @@ This feature is fit for a large number of short connections.
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.
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
@@ -432,15 +648,53 @@ custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
```
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
### Set Headers In HTTP Request
You can set headers for proxy which type is `http`.
```ini
# frpc.ini
[web]
type = http
local_port = 80
custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com
header_X-From-Where = frp
```
Note that params which have prefix `header_` will be added to http request headers.
In this example, it will set header `X-From-Where: frp` to http request.
### Get Real IP
#### HTTP X-Forwarded-For
Features for http proxy only.
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
**Note that now you can only get these two headers in first request of each user connection.**
#### 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
@@ -521,11 +775,26 @@ 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** and you can see [example usage](#example-usage).
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.
@@ -543,16 +812,9 @@ 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.
* Direct reverse proxy, like haproxy.
* Load balance to different service in frpc.
* Frpc can directly be a webserver for static files.
* P2p communicate by make udp hole to penetrate NAT.
* kubernetes ingress support.
## Contributing

View File

@@ -4,57 +4,64 @@
[README](README.md) | [中文文档](README_zh.md)
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http https 应用协议提供了额外的能力,且尝试性支持了点对点穿透
## 目录
<!-- vim-markdown-toc GFM -->
* [frp 的作用](#frp-的作用)
* [开发状态](#开发状态)
* [架构](#架构)
* [使用示例](#使用示例)
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字)
* [转发 Unix 域套接字](#转发-unix-域套接字)
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
* [安全地暴露内网服务](#安全地暴露内网服务)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
* [点对点内网穿透](#点对点内网穿透)
* [功能说明](#功能说明)
* [配置文件](#配置文件)
* [配置文件模版渲染](#配置文件模版渲染)
* [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 的作用
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
* 对于 http, https 服务支持基于域名的虚拟主机支持自定义域名绑定使多个域名可以共用一个80端口。
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
## 开发状态
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境。
frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。
master 分支用于发布稳定版本dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
@@ -122,7 +129,7 @@ master 分支用于发布稳定版本dev 分支用于开发,您可以尝试
vhost_http_port = 8080
```
2. 启动 frps
2. 启动 frps
`./frps -c ./frps.ini`
@@ -185,15 +192,15 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
`dig @x.x.x.x -p 6000 www.goolge.com`
`dig @x.x.x.x -p 6000 www.google.com`
### 转发 Unix域套接字
### 转发 Unix 域套接字
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
frps 的部署步骤同上。
1. 启动 frpc启用 unix_domain_socket 插件,配置如下:
1. 启动 frpc启用 `unix_domain_socket` 插件,配置如下:
```ini
# frpc.ini
@@ -212,6 +219,62 @@ frps 的部署步骤同上。
`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
plugin_header_X-From-Where = frp
```
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
### 安全地暴露内网服务
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
@@ -246,10 +309,10 @@ frps 的部署步骤同上。
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
[secret_ssh_visitor]
type = stcp
# stcp 的访问者
role = vistor
role = visitor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
sk = abcdefg
@@ -262,27 +325,59 @@ frps 的部署步骤同上。
`ssh -oPort=6000 test@127.0.0.1`
### 通过 frpc 所在机器访问外网
### 点对点内网穿透
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景
frps 的部署步骤同上
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接
1. 启动 frpc启用 http_proxy 或 socks5 插件(plugin 换为 socks5 即可) 配置如下:
目前处于开发的初级阶段,并不能穿透所有类型的 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
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy
[p2p_ssh]
type = xtcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
2. 浏览器设置 http 或 socks5 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
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`
## 功能说明
@@ -294,10 +389,41 @@ frps 的部署步骤同上。
[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
@@ -312,13 +438,27 @@ dashboard_pwd = 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 端口映射出去即可,但需要重视安全风险。
### 身份验证
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式)服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
这个超时时间可以在配置文件中通过 `authentication_timeout` 这个参数来修改,单位为秒,默认值为 900即 15 分钟。如果修改为 0则 frps 将不对身份验证报文的时间戳进行超时校验。
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
### 加密与压缩
@@ -338,9 +478,17 @@ use_compression = true
如果传输的报文长度较长,通过设置 `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 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
@@ -353,27 +501,35 @@ admin_port = 7400
之后执行重启命令:
`frpc -c ./frpc.ini --reload`
`frpc reload -c ./frpc.ini`
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
### 特权模式
### 客户端查看代理状态
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口
#### 端口白名单
### 端口白名单
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
```ini
# frps.ini
[common]
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
allow_ports = 2000-3000,3001,3003,4000-50000
```
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
`allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### 端口复用
目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口frps 会对连接的协议进行分析,之后进行不同的处理。
例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。
后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。
### TCP 多路复用
@@ -389,7 +545,7 @@ tcp_mux = false
### 底层通信可选 kcp 协议
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
开启 kcp 协议支持:
@@ -438,6 +594,81 @@ tcp_mux = false
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 类型的代理。
@@ -453,11 +684,52 @@ 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** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
#### HTTP X-Forwarded-For
**需要注意的是,目前只在每一个用户连接的第一个 HTTP 请求中添加了这两个 header。**
目前只有 **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 服务
@@ -503,9 +775,9 @@ local_port = 80
subdomain = test
```
frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。
需要注意的是如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。
**注:如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。**
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
@@ -548,11 +820,30 @@ 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**。具体使用方式请查看[使用示例](#使用示例)。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
@@ -575,11 +866,6 @@ plugin_http_passwd = abc
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
* frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。
* frpc 支持直接作为 webserver 访问指定静态页面。
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
* 集成对 k8s 等平台的支持。
## 为 frp 做贡献
@@ -600,6 +886,12 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
frp 交流群606194980 (QQ 群号)
### 知识星球
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
![zsxq](/doc/pic/zsxq.jpg)
### 支付宝扫码捐赠
![donate-alipay](/doc/pic/donate-alipay.png)

View File

@@ -14,8 +14,10 @@
package assets
//go:generate statik -src=./static
//go:generate go fmt statik/statik.go
//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"
@@ -24,8 +26,6 @@ import (
"path"
"github.com/rakyll/statik/fs"
_ "github.com/fatedier/frp/assets/statik"
)
var (

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

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

View File

@@ -1 +0,0 @@
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5217927b66cc446ebfd3"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?bf962cded96400bef9a0"></script></body> </html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"bf962cded96400bef9a0",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,10 +20,10 @@ import (
"net/http"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/assets"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
"github.com/gorilla/mux"
)
var (
@@ -31,14 +31,25 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// url router
router := httprouter.New()
router := mux.NewRouter()
user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
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{

View File

@@ -16,63 +16,310 @@ package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Code int
Msg string
}
// api/reload
type ReloadResp struct {
GeneralResponse
// 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(svr.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(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(svr.cfg.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
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
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, serverAddr string) 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", serverAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = 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", serverAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = 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 ReloadResp
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/reload]: code [%d]", res.Code)
log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/reload]")
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps {
switch status.Type {
case "tcp":
res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "udp":
res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "http":
res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "https":
res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "stcp":
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "xtcp":
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
}
}
newCommonCfg, err := config.LoadClientCommonConf(conf)
if err != nil {
res.Code = 2
res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err)
return
}
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
if err != nil {
res.Code = 3
res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err)
return
}
svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
log.Info("success reload conf")
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 svr.cfgFile == "" {
res.Code = 400
res.Msg = "frpc has no config file path"
log.Warn("%s", res.Msg)
return
}
content, err := config.GetRenderedConfFromFile(svr.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(svr.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(svr.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
}
}

View File

@@ -15,51 +15,40 @@
package client
import (
"crypto/tls"
"fmt"
"io"
"runtime"
"runtime/debug"
"sync"
"time"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"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"
"github.com/xtaci/smux"
)
const (
connReadTimeout time.Duration = 10 * time.Second
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
fmux "github.com/hashicorp/yamux"
)
type Control struct {
// frpc service
svr *Service
// uniq id got from frps, attach it in loginMsg
runId string
// login message to server
loginMsg *msg.Login
// proxy configures
// manage all proxies
pxyCfgs map[string]config.ProxyConf
pm *proxy.ProxyManager
// proxies
proxies map[string]Proxy
// vistor configures
vistorCfgs map[string]config.ProxyConf
// vistors
vistors map[string]Vistor
// manage all visitors
vm *VisitorManager
// control connection
conn frpNet.Conn
// tcp stream multiplexing, if enabled
session *smux.Session
session *fmux.Session
// put a message in this channel to send it over control connection to server
sendCh chan (msg.Message)
@@ -67,105 +56,74 @@ type Control struct {
// read from this channel to get the next message sent by server
readCh chan (msg.Message)
// run id got from server
runId string
// if we call close() in control, do not reconnect to server
exit bool
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
closedCh chan int
closedCh chan struct{}
closedDoneCh chan struct{}
// last time got the Pong message
lastPong time.Time
// The client configuration
clientCfg config.ClientCommonConf
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
msgHandlerShutdown *shutdown.Shutdown
// The UDP port that the server is listening on
serverUDPPort int
mu sync.RWMutex
log.Logger
}
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: config.ClientCommonCfg.PoolCount,
User: config.ClientCommonCfg.User,
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
vistorCfgs: vistorCfgs,
proxies: make(map[string]Proxy),
vistors: make(map[string]Vistor),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *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{}),
clientCfg: clientCfg,
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(),
serverUDPPort: serverUDPPort,
Logger: log.NewPrefixLogger(""),
}
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
ctl.vm = NewVisitorManager(ctl)
ctl.vm.Reload(visitorCfgs)
return ctl
}
// 1. login
// 2. start reader() writer() manager()
// 3. connection closed
// 4. In reader(): close closedCh and exit, controler() get it
// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
// 6. In controler(): ini readCh, sendCh, closedCh
// 7. In controler(): start new reader(), writer(), manager()
// controler() will keep running
func (ctl *Control) Run() (err error) {
for {
err = ctl.login()
if err != nil {
ctl.Warn("login to server failed: %v", err)
func (ctl *Control) Run() {
go ctl.worker()
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and continues relogin to server
if config.ClientCommonCfg.LoginFailExit {
return
} else {
time.Sleep(30 * time.Second)
}
} else {
break
}
}
// start all proxies
ctl.pm.Reload(ctl.pxyCfgs)
go ctl.controler()
go ctl.manager()
go ctl.writer()
go ctl.reader()
// start all local vistors
for _, cfg := range ctl.vistorCfgs {
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
return nil
// start all visitors
go ctl.vm.Run()
return
}
func (ctl *Control) NewWorkConn() {
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
workConn, err := ctl.connectServer()
if err != nil {
return
}
m := &msg.NewWorkConn{
RunId: ctl.getRunId(),
RunId: ctl.runId,
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
@@ -182,108 +140,37 @@ func (ctl *Control) NewWorkConn() {
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
pxy, ok := ctl.getProxy(startMsg.ProxyName)
if ok {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
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 {
workConn.Close()
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
}
}
func (ctl *Control) Close() error {
ctl.mu.Lock()
ctl.exit = true
err := errors.PanicToError(func() {
for name, _ := range ctl.proxies {
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
})
ctl.mu.Unlock()
return err
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
}
// login send a login message to server and wait for a loginResp message.
func (ctl *Control) login() (err error) {
if ctl.conn != nil {
ctl.conn.Close()
}
ctl.pm.Close()
ctl.conn.Close()
if ctl.session != nil {
ctl.session.Close()
}
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
return err
}
defer func() {
if err != nil {
conn.Close()
}
}()
if config.ClientCommonCfg.TcpMux {
session, errRet := smux.Client(conn, nil)
if errRet != nil {
return errRet
}
stream, errRet := session.OpenStream()
if errRet != nil {
session.Close()
return errRet
}
conn = frpNet.WrapConn(stream)
ctl.session = session
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.getRunId()
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
}
var loginRespMsg msg.LoginResp
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
return err
}
conn.SetReadDeadline(time.Time{})
if loginRespMsg.Error != "" {
err = fmt.Errorf("%s", loginRespMsg.Error)
ctl.Error("%s", loginRespMsg.Error)
return err
}
ctl.conn = conn
// update runId got from server
ctl.setRunId(loginRespMsg.RunId)
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
// login success, so we let closedCh available again
ctl.closedCh = make(chan int)
ctl.lastPong = time.Now()
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 config.ClientCommonCfg.TcpMux {
if ctl.clientCfg.TcpMux {
stream, errRet := ctl.session.OpenStream()
if errRet != nil {
err = errRet
@@ -291,10 +178,15 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
return
}
conn = frpNet.WrapConn(stream)
} else {
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
var tlsConfig *tls.Config
if ctl.clientCfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
if err != nil {
ctl.Warn("start new connection to server error: %v", err)
return
@@ -303,15 +195,18 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
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(config.ClientCommonCfg.PrivilegeToken))
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
@@ -319,6 +214,7 @@ func (ctl *Control) reader() {
return
} else {
ctl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
} else {
@@ -327,8 +223,10 @@ func (ctl *Control) reader() {
}
}
// writer writes messages got from sendCh to frps
func (ctl *Control) writer() {
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.conn.Close()
@@ -347,19 +245,23 @@ func (ctl *Control) writer() {
}
}
// manager handles all channel events and do corresponding process
func (ctl *Control) manager() {
// 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(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
ctl.lastPong = time.Now()
for {
select {
case <-hbSend.C:
@@ -367,7 +269,7 @@ func (ctl *Control) manager() {
ctl.Debug("send heartbeat to server")
ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout")
// let reader() stop
ctl.conn.Close()
@@ -380,35 +282,9 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.ReqWorkConn:
go ctl.NewWorkConn()
go ctl.HandleReqWorkConn(m)
case *msg.NewProxyResp:
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
if m.Error != "" {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
cfg, ok := ctl.getProxyConf(m.ProxyName)
if !ok {
// it will never go to this branch now
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
continue
}
oldPxy, ok := ctl.getProxy(m.ProxyName)
if ok {
oldPxy.Close()
}
pxy := NewProxy(ctl, cfg)
if err := pxy.Run(); err != nil {
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: m.ProxyName,
}
continue
}
ctl.addProxy(m.ProxyName, pxy)
ctl.Info("[%s] start proxy success", m.ProxyName)
ctl.HandleNewProxyResp(m)
case *msg.Pong:
ctl.lastPong = time.Now()
ctl.Debug("receive heartbeat from server")
@@ -417,205 +293,35 @@ func (ctl *Control) manager() {
}
}
// controler keep watching closedCh, start a new connection if previous control connection is closed.
// If controler is notified by closedCh, reader and writer and manager will exit, then recall these functions.
func (ctl *Control) controler() {
var err error
maxDelayTime := 30 * time.Second
delayTime := time.Second
// If controler is notified by closedCh, reader and writer and handler will exit
func (ctl *Control) worker() {
go ctl.msgHandler()
go ctl.reader()
go ctl.writer()
checkInterval := 10 * time.Second
checkProxyTicker := time.NewTicker(checkInterval)
for {
select {
case <-checkProxyTicker.C:
// Every 10 seconds, check which proxy registered failed and reregister it to server.
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to register proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
select {
case <-ctl.closedCh:
// close related channels and wait until other goroutines done
close(ctl.readCh)
ctl.readerShutdown.WaitDone()
ctl.msgHandlerShutdown.WaitDone()
for _, cfg := range ctl.vistorCfgs {
if _, exist := ctl.vistors[cfg.GetName()]; !exist {
ctl.Info("try to start vistor [%s]", cfg.GetName())
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
}
ctl.mu.RUnlock()
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
// close related channels
close(ctl.readCh)
close(ctl.sendCh)
close(ctl.sendCh)
ctl.writerShutdown.WaitDone()
for _, pxy := range ctl.proxies {
pxy.Close()
}
// if ctl.exit is true, just exit
ctl.mu.RLock()
exit := ctl.exit
ctl.mu.RUnlock()
if exit {
return
}
ctl.pm.Close()
ctl.vm.Close()
time.Sleep(time.Second)
// loop util reconnect to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
if err != nil {
ctl.Warn("reconnect to server error: %v", err)
time.Sleep(delayTime)
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
continue
}
// reconnect success, init the delayTime
delayTime = time.Second
break
}
// init related channels and variables
ctl.init()
// previous work goroutines should be closed and start them here
go ctl.manager()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
ctl.mu.RUnlock()
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
}
close(ctl.closedDoneCh)
if ctl.session != nil {
ctl.session.Close()
}
return
}
}
func (ctl *Control) setRunId(runId string) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.runId = runId
}
func (ctl *Control) getRunId() string {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
return ctl.runId
}
func (ctl *Control) getProxy(name string) (pxy Proxy, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
pxy, ok = ctl.proxies[name]
return
}
func (ctl *Control) addProxy(name string, pxy Proxy) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.proxies[name] = pxy
}
func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
conf, ok = ctl.pxyCfgs[name]
return
}
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
removedPxyNames := make([]string, 0)
for name, oldCfg := range ctl.pxyCfgs {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}
if del {
removedPxyNames = append(removedPxyNames, name)
delete(ctl.pxyCfgs, name)
if pxy, ok := ctl.proxies[name]; ok {
pxy.Close()
}
delete(ctl.proxies, name)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
}
ctl.Info("proxy removed: %v", removedPxyNames)
addedPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := ctl.pxyCfgs[name]; !ok {
ctl.pxyCfgs[name] = cfg
addedPxyNames = append(addedPxyNames, name)
}
}
ctl.Info("proxy added: %v", addedPxyNames)
removedVistorName := make([]string, 0)
for name, oldVistorCfg := range ctl.vistorCfgs {
del := false
cfg, ok := vistorCfgs[name]
if !ok {
del = true
} else {
if !oldVistorCfg.Compare(cfg) {
del = true
}
}
if del {
removedVistorName = append(removedVistorName, name)
delete(ctl.vistorCfgs, name)
if vistor, ok := ctl.vistors[name]; ok {
vistor.Close()
}
delete(ctl.vistors, name)
}
}
ctl.Info("vistor removed: %v", removedVistorName)
addedVistorName := make([]string, 0)
for name, vistorCfg := range vistorCfgs {
if _, ok := ctl.vistorCfgs[name]; !ok {
ctl.vistorCfgs[name] = vistorCfg
addedVistorName = append(addedVistorName, name)
}
}
ctl.Info("vistor added: %v", addedVistorName)
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 {
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
err := monitor.doCheck(doCtx)
// check if this monitor has been closed
select {
case <-monitor.ctx.Done():
cancel()
return
default:
cancel()
}
if err == nil {
if monitor.l != nil {
monitor.l.Trace("do one health check success")
}
if !monitor.statusOK && monitor.statusNormalFn != nil {
if monitor.l != nil {
monitor.l.Info("health check status change to success")
}
monitor.statusOK = true
monitor.statusNormalFn()
}
} else {
if monitor.l != nil {
monitor.l.Warn("do one health check failed: %v", err)
}
monitor.failedTimes++
if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
if monitor.l != nil {
monitor.l.Warn("health check status change to failed")
}
monitor.statusOK = false
monitor.statusFailedFn()
}
}
time.Sleep(monitor.interval)
}
}
func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
switch monitor.checkType {
case "tcp":
return monitor.doTcpCheck(ctx)
case "http":
return monitor.doHttpCheck(ctx)
default:
return ErrHealthCheckType
}
}
func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
// if tcp address is not specified, always return nil
if monitor.addr == "" {
return nil
}
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", monitor.addr)
if err != nil {
return err
}
conn.Close()
return nil
}
func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
req, err := http.NewRequest("GET", monitor.url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body)
if resp.StatusCode/100 != 2 {
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
}
return nil
}

View File

@@ -1,341 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/plugin"
"github.com/fatedier/frp/models/proto/udp"
"github.com/fatedier/frp/utils/errors"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
)
// Proxy defines how to deal with work connections for different proxy type.
type Proxy interface {
Run() error
// InWorkConn accept work connections registered to server.
InWorkConn(conn frpNet.Conn)
Close()
log.Logger
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
baseProxy := BaseProxy{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
}
return
}
type BaseProxy struct {
ctl *Control
closed bool
mu sync.RWMutex
log.Logger
}
// TCP
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *TcpProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
return
}
}
return
}
func (pxy *TcpProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// HTTP
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *HttpProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
return
}
}
return
}
func (pxy *HttpProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// HTTPS
type HttpsProxy struct {
BaseProxy
cfg *config.HttpsProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *HttpsProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
return
}
}
return
}
func (pxy *HttpsProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// 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) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// UDP
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
localAddr *net.UDPAddr
readCh chan *msg.UdpPacket
// include msg.UdpPacket and msg.Ping
sendCh chan msg.Message
workConn frpNet.Conn
}
func (pxy *UdpProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
if err != nil {
return
}
return
}
func (pxy *UdpProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
if !pxy.closed {
pxy.closed = true
if pxy.workConn != nil {
pxy.workConn.Close()
}
if pxy.readCh != nil {
close(pxy.readCh)
}
if pxy.sendCh != nil {
close(pxy.sendCh)
}
}
}
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
// close resources releated with old workConn
pxy.Close()
pxy.mu.Lock()
pxy.workConn = conn
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.sendCh = make(chan msg.Message, 1024)
pxy.closed = false
pxy.mu.Unlock()
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
for {
var udpMsg msg.UdpPacket
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
pxy.Warn("read from workConn for udp error: %v", errRet)
return
}
if errRet := errors.PanicToError(func() {
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
readCh <- &udpMsg
}); errRet != nil {
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
return
}
}
}
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
defer func() {
pxy.Info("writer goroutine for udp work connection closed")
}()
var errRet error
for rawMsg := range sendCh {
switch m := rawMsg.(type) {
case *msg.UdpPacket:
pxy.Trace("send udp package to workConn: %s", m.Content)
case *msg.Ping:
pxy.Trace("send ping message to udp workConn")
}
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
pxy.Error("udp work write error: %v", errRet)
return
}
}
}
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
var errRet error
for {
time.Sleep(time.Duration(30) * time.Second)
if errRet = errors.PanicToError(func() {
sendCh <- &msg.Ping{}
}); errRet != nil {
pxy.Trace("heartbeat goroutine for udp work connection closed")
break
}
}
}
go workConnSenderFn(pxy.workConn, pxy.sendCh)
go workConnReaderFn(pxy.workConn, pxy.readCh)
go heartbeatFn(pxy.workConn, pxy.sendCh)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
}
// Common handler for tcp work connections.
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
var (
remote io.ReadWriteCloser
err error
)
remote = workConn
if baseInfo.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
if err != nil {
workConn.Error("create encryption stream error: %v", err)
return
}
}
if baseInfo.UseCompression {
remote = frpIo.WithCompression(remote)
}
if proxyPlugin != nil {
// if plugin is set, let plugin handle connections first
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
proxyPlugin.Handle(remote)
workConn.Debug("handle by plugin finished")
return
} else {
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
if err != nil {
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
return
}
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
frpIo.Join(localConn, remote)
workConn.Debug("join connections closed")
}
}

566
client/proxy/proxy.go Normal file
View File

@@ -0,0 +1,566 @@
// 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/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, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
baseProxy := BaseProxy{
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
clientCfg: clientCfg,
serverUDPPort: serverUDPPort,
}
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
clientCfg config.ClientCommonConf
serverUDPPort int
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(pxy.clientCfg.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(pxy.clientCfg.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(pxy.clientCfg.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(pxy.clientCfg.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", pxy.clientCfg.ServerAddr, pxy.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 strings.Contains(m.SrcAddr, ".") {
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,146 @@
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
clientCfg config.ClientCommonConf
// The UDP port that the server is listening on
serverUDPPort int
logPrefix string
log.Logger
}
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
return &ProxyManager{
proxies: make(map[string]*ProxyWrapper),
sendCh: msgSendCh,
closed: false,
clientCfg: clientCfg,
serverUDPPort: serverUDPPort,
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.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
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, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *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, clientCfg, serverUDPPort)
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
}

View File

@@ -15,44 +15,237 @@
package client
import (
"crypto/tls"
"fmt"
"io/ioutil"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/fatedier/frp/assets"
"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"
)
// Service is a client service.
type Service struct {
// manager control connection with server
ctl *Control
// uniq id got from frps, attach it in loginMsg
runId string
// manager control connection with server
ctl *Control
ctlMu sync.RWMutex
cfg config.ClientCommonConf
pxyCfgs map[string]config.ProxyConf
visitorCfgs map[string]config.VisitorConf
cfgMu sync.RWMutex
// The configuration file used to initialize this client, or an empty
// string if no configuration file was used.
cfgFile string
// This is configured by the login response from frps
serverUDPPort int
exit uint32 // 0 means not exit
closedCh chan int
}
func NewService(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) (svr *Service) {
// NewService creates a new client service with the given configuration.
func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
svr = &Service{
closedCh: make(chan int),
cfg: cfg,
cfgFile: cfgFile,
pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs,
exit: 0,
closedCh: make(chan int),
}
ctl := NewControl(svr, pxyCfgs, vistorCfgs)
svr.ctl = ctl
return
}
func (svr *Service) GetController() *Control {
svr.ctlMu.RLock()
defer svr.ctlMu.RUnlock()
return svr.ctl
}
func (svr *Service) Run() error {
err := svr.ctl.Run()
if err != nil {
return err
// 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 svr.cfg.LoginFailExit {
return err
} else {
time.Sleep(10 * time.Second)
}
} else {
// login success
ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
ctl.Run()
svr.ctlMu.Lock()
svr.ctl = ctl
svr.ctlMu.Unlock()
break
}
}
if config.ClientCommonCfg.AdminPort != 0 {
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
go svr.keepControllerWorking()
if svr.cfg.AdminPort != 0 {
// Init admin server assets
err := assets.Load(svr.cfg.AssetsDir)
if err != nil {
return fmt.Errorf("Load assets error: %v", err)
}
err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
if err != nil {
log.Warn("run admin server error: %v", err)
}
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
}
<-svr.closedCh
return nil
}
func (svr *Service) Close() error {
return svr.ctl.Close()
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.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
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 svr.cfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
if err != nil {
return
}
defer func() {
if err != nil {
conn.Close()
if session != nil {
session.Close()
}
}
}()
if svr.cfg.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: svr.cfg.PoolCount,
User: svr.cfg.User,
Version: version.Full(),
PrivilegeKey: util.GetAuthKey(svr.cfg.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
svr.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)
}

324
client/visitor.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 client
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"sync"
"time"
"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 sv.ctl.serverUDPPort == 0 {
sv.Error("xtcp is not supported by server")
return
}
raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.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

@@ -1,145 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"io"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
)
// Vistor is used for forward traffics from local port tot remote service.
type Vistor interface {
Run() error
Close()
log.Logger
}
func NewVistor(ctl *Control, pxyConf config.ProxyConf) (vistor Vistor) {
baseVistor := BaseVistor{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
case *config.StcpProxyConf:
vistor = &StcpVistor{
BaseVistor: baseVistor,
cfg: cfg,
}
}
return
}
type BaseVistor struct {
ctl *Control
l frpNet.Listener
closed bool
mu sync.RWMutex
log.Logger
}
type StcpVistor struct {
BaseVistor
cfg *config.StcpProxyConf
}
func (sv *StcpVistor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
if err != nil {
return
}
go sv.worker()
return
}
func (sv *StcpVistor) Close() {
sv.l.Close()
}
func (sv *StcpVistor) worker() {
for {
conn, err := sv.l.Accept()
if err != nil {
sv.Warn("stcp local listener closed")
return
}
go sv.handleConn(conn)
}
}
func (sv *StcpVistor) handleConn(userConn frpNet.Conn) {
defer userConn.Close()
sv.Debug("get a new stcp user connection")
vistorConn, err := sv.ctl.connectServer()
if err != nil {
return
}
defer vistorConn.Close()
now := time.Now().Unix()
newVistorConnMsg := &msg.NewVistorConn{
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
}
err = msg.WriteMsg(vistorConn, newVistorConnMsg)
if err != nil {
sv.Warn("send newVistorConnMsg to server error: %v", err)
return
}
var newVistorConnRespMsg msg.NewVistorConnResp
vistorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(vistorConn, &newVistorConnRespMsg)
if err != nil {
sv.Warn("get newVistorConnRespMsg error: %v", err)
return
}
vistorConn.SetReadDeadline(time.Time{})
if newVistorConnRespMsg.Error != "" {
sv.Warn("start new vistor connection error: %s", newVistorConnRespMsg.Error)
return
}
var remote io.ReadWriteCloser
remote = vistorConn
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)
}

View File

@@ -15,175 +15,18 @@
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"math/rand"
"time"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
_ "github.com/fatedier/frp/assets/frpc/statik"
"github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
"github.com/fatedier/golib/crypto"
)
var (
configFile string = "./frpc.ini"
)
var usage string = `frpc is the client of frp
Usage:
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
frpc [-c config_file] --reload
frpc -h | --help
frpc -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
--reload reload configure file without program exit
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frps.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
crypto.DefaultSalt = "frp"
rand.Seed(time.Now().UnixNano())
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg.ConfigFile = confFile
// check if reload command
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ClientCommonCfg.LogWay = "console"
} else {
config.ClientCommonCfg.LogWay = "file"
config.ClientCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
addr := strings.Split(args["--server-addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = serverPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
svr := client.NewService(pxyCfgs, vistorCfgs)
// Capture the exit signal if we use kcp.
if config.ClientCommonCfg.Protocol == "kcp" {
go HandleSignal(svr)
}
err = svr.Run()
if err != nil {
fmt.Println(err)
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)
os.Exit(0)
sub.Execute()
}

97
cmd/frpc/sub/http.go Normal file
View File

@@ -0,0 +1,97 @@
// 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

89
cmd/frpc/sub/https.go Normal file
View File

@@ -0,0 +1,89 @@
// 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

90
cmd/frpc/sub/reload.go Normal file
View File

@@ -0,0 +1,90 @@
// 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/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)
}
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = reload(clientCfg)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
fmt.Printf("reload success\n")
return nil
},
}
func reload(clientCfg config.ClientCommonConf) error {
if clientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
}
req, err := http.NewRequest("GET", "http://"+
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
clientCfg.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)))
}

228
cmd/frpc/sub/root.go Normal file
View File

@@ -0,0 +1,228 @@
// 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/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
disableLogColor bool
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) (cfg config.ClientCommonConf, err error) {
if fileType == CfgFileTypeIni {
cfg, err = parseClientCommonCfgFromIni(content)
} else if fileType == CfgFileTypeCmd {
cfg, err = parseClientCommonCfgFromCmd()
}
if err != nil {
return
}
err = cfg.Check()
if err != nil {
return
}
return
}
func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
cfg, err := config.UnmarshalClientConfFromIni(content)
if err != nil {
return config.ClientCommonConf{}, err
}
return cfg, err
}
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf()
strs := strings.Split(serverAddr, ":")
if len(strs) < 2 {
err = fmt.Errorf("invalid server_addr")
return
}
if strs[0] != "" {
cfg.ServerAddr = strs[0]
}
cfg.ServerPort, err = strconv.Atoi(strs[1])
if err != nil {
err = fmt.Errorf("invalid server_addr")
return
}
cfg.User = user
cfg.Protocol = protocol
cfg.Token = token
cfg.LogLevel = logLevel
cfg.LogFile = logFile
cfg.LogMaxDays = int64(logMaxDays)
if logFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
cfg.DisableLogColor = disableLogColor
return
}
func runClient(cfgFilePath string) (err error) {
var content string
content, err = config.GetRenderedConfFromFile(cfgFilePath)
if err != nil {
return
}
cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
if err != nil {
return
}
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
if err != nil {
return err
}
err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
return
}
func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
cfg.LogMaxDays, cfg.DisableLogColor)
if cfg.DnsServer != "" {
s := cfg.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(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
}
// Capture the exit signal if we use kcp.
if cfg.Protocol == "kcp" {
go handleSignal(svr)
}
err = svr.Run()
if cfg.Protocol == "kcp" {
<-kcpDoneCh
}
return
}

154
cmd/frpc/sub/status.go Normal file
View File

@@ -0,0 +1,154 @@
// 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/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)
}
clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = status(clientCfg)
if err != nil {
fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func status(clientCfg config.ClientCommonConf) error {
if clientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
}
req, err := http.NewRequest("GET", "http://"+
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
clientCfg.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
}

114
cmd/frpc/sub/stcp.go Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, visitorConfs, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

86
cmd/frpc/sub/tcp.go Normal file
View File

@@ -0,0 +1,86 @@
// 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

86
cmd/frpc/sub/udp.go Normal file
View File

@@ -0,0 +1,86 @@
// 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

114
cmd/frpc/sub/xtcp.go Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 {
clientCfg, 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(clientCfg, proxyConfs, visitorConfs, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,104 +15,17 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"math/rand"
"time"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/golib/crypto"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
_ "github.com/fatedier/frp/assets/frps/statik"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frps.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
crypto.DefaultSalt = "frp"
rand.Seed(time.Now().UnixNano())
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ServerCommonCfg.LogWay = "console"
} else {
config.ServerCommonCfg.LogWay = "file"
config.ServerCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ServerCommonCfg.BindAddr = addr[0]
config.ServerCommonCfg.BindPort = bindPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frps success")
if config.ServerCommonCfg.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
server.ServerService = svr
svr.Run()
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/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
disableLogColor bool
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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
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 cfg config.ServerCommonConf
var err error
if cfgFile != "" {
var content string
content, err = config.GetRenderedConfFromFile(cfgFile)
if err != nil {
return err
}
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
} else {
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
}
if err != nil {
return err
}
err = runServer(cfg)
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) (cfg config.ServerCommonConf, err error) {
if fileType == CfgFileTypeIni {
cfg, err = parseServerCommonCfgFromIni(content)
} else if fileType == CfgFileTypeCmd {
cfg, err = parseServerCommonCfgFromCmd()
}
if err != nil {
return
}
err = cfg.Check()
if err != nil {
return
}
return
}
func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
cfg, err := config.UnmarshalServerConfFromIni(content)
if err != nil {
return config.ServerCommonConf{}, err
}
return cfg, nil
}
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg = config.GetDefaultServerConf()
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
cfg.BindUdpPort = bindUdpPort
cfg.KcpBindPort = kcpBindPort
cfg.ProxyBindAddr = proxyBindAddr
cfg.VhostHttpPort = vhostHttpPort
cfg.VhostHttpsPort = vhostHttpsPort
cfg.VhostHttpTimeout = vhostHttpTimeout
cfg.DashboardAddr = dashboardAddr
cfg.DashboardPort = dashboardPort
cfg.DashboardUser = dashboardUser
cfg.DashboardPwd = dashboardPwd
cfg.LogFile = logFile
cfg.LogLevel = logLevel
cfg.LogMaxDays = logMaxDays
cfg.Token = token
cfg.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 {
cfg.AllowPorts[int(port)] = struct{}{}
}
}
cfg.MaxPortsPerClient = maxPortsPerClient
if logFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
cfg.DisableLogColor = disableLogColor
return
}
func runServer(cfg config.ServerCommonConf) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
svr, err := server.NewService(cfg)
if err != nil {
return err
}
log.Info("Start frps success")
svr.Run()
return
}

View File

@@ -5,9 +5,10 @@
server_addr = 0.0.0.0
server_port = 7000
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
# 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:pwd@192.168.1.128:8080
# 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
@@ -17,14 +18,19 @@ log_level = info
log_max_days = 3
# disable log colors when log_file is console, default is false
disable_log_color = false
# for authentication
privilege_token = 12345678
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
# Admin assets directory. By default, these assets are bundled with frpc.
# assets_dir = ./static
# connections will be established in advance, default value is zero
pool_count = 5
@@ -40,10 +46,16 @@ user = your_name
login_fail_exit = true
# communication protocol used to connect to server
# now it supports tcp and kcp, default is tcp
# now it supports tcp and kcp and websocket, default is tcp
protocol = tcp
# proxy names you want to start divided by ','
# 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
@@ -52,10 +64,10 @@ protocol = tcp
# heartbeat_interval = 30
# heartbeat_timeout = 90
# ssh is the proxy name same as server's configuration
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
# 'ssh' 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, default is tcp
# tcp | udp | http | https | stcp | xtcp, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
@@ -65,6 +77,36 @@ use_encryption = false
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
@@ -74,6 +116,14 @@ 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
@@ -88,18 +138,30 @@ http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
subdomain = web01
custom_domains = web02.yourdomain.com
# locations is only useful for http type
# locations 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
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
@@ -107,7 +169,7 @@ remote_port = 6003
# if plugin is defined, local_ip and local_port is useless
# plugin will handle connections got from frps
plugin = unix_domain_socket
# params set with prefix "plugin_" that plugin needed
# params with prefix "plugin_" that plugin needed
plugin_unix_path = /var/run/docker.sock
[plugin_http_proxy]
@@ -117,27 +179,71 @@ plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
[plugin_socks5]
type = tcp
remote_port = 6005
plugin = socks5
plugin_user = abc
plugin_passwd = abc
[plugin_static_file]
type = tcp
remote_port = 6006
plugin = static_file
plugin_local_path = /var/www/blog
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
[plugin_https2http]
type = https
custom_domains = test.yourdomain.com
plugin = https2http
plugin_local_addr = 127.0.0.1:80
plugin_crt_path = ./server.crt
plugin_key_path = ./server.key
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp
[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 vistor
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
type = stcp
# sk used for authentication for vistors
# 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 vistor
[secret_tcp_vistor]
# frpc role vistor -> frps -> frpc role server
role = vistor
# 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 vistor
# the server name you want to visitor
server_name = secret_tcp
sk = abcdefg
# connect this address to vistor stcp server
# 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

@@ -5,6 +5,9 @@
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
@@ -13,13 +16,20 @@ kcp_bind_port = 7000
# 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
# set dashboard_port to view dashboard of frps
# 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 pwd for basic auth protect, if not set, both default value is admin
# dashboard user and passwd for basic auth protect, if not set, both default value is admin
dashboard_user = admin
dashboard_pwd = admin
@@ -33,22 +43,24 @@ log_level = info
log_max_days = 3
# privilege mode is the only supported mode since v0.10.0
privilege_token = 12345678
# disable log colors when log_file is console, default is false
disable_log_color = false
# 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
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
allow_ports = 2000-3000,3001,3003,4000-50000
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
max_pool_count = 5
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
# if authentication_timeout is zero, the time is not verified, default is 900s
authentication_timeout = 900
# 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
@@ -56,3 +68,6 @@ 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/zsxq.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,135 +0,0 @@
# Quick Start
frp is easier to use compared with other similar projects.
We will use two simple demo to demonstrate how to use frp.
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
### Download SourceCode
`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`.
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
### Compile
Enter the root directory and execute `make`, then wait until finished.
**bin** include all executable programs when **conf** include corresponding configuration files.
### Pre-requirement
* Go environment. Version of go >= 1.4.
* Godep (if not exist, `go get` will be executed to download godep when compiling)
### Deploy
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
3. Modify all configuration files, details in next paragraph.
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
## Tcp port forwarding
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# for accept connections from frpc
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# finally we connect to server A by this port
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# server address of frps
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# for authentication
auth_token = 123
# ssh is proxy name same with configure in frps.ini
[ssh]
# local port which need to be transferred
local_port = 22
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
use_encryption = true
```
## Http port forwarding and Custom domains binding
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
After that, you can visit your web pages in local server by custom domains.
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# if you want to support vhost, specify one port for http services
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
type = http
auth_token = 123
# # if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# custom domains are set in frps.ini
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# encryption is optional, default is false
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

View File

@@ -1,137 +0,0 @@
# frp 使用文档
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x测试时替换为真实的IP地址
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
### 下载源码
推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
### 编译
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。
### 依赖
* go 1.4 以上版本
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
### 部署
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**{user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
## tcp 端口转发
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# 用于接收 frpc 连接的端口
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh 为代理的自定义名称可以有多个不能重复和frpc中名称对应
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# 最后将通过此端口访问后端服务
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# frps 所在服务器绑定的IP地址
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# 用于身份验证
auth_token = 123
# ssh 需要和 frps.ini 中配置一致
[ssh]
# 需要转发的本地端口
local_port = 22
# 启用加密frpc与frps之间通信加密默认为 false
use_encryption = true
```
## http 端口转发,自定义域名绑定
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口则需要指定代理的类型为 http并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
之后就可以通过自定义域名访问到本地的多个 web 服务。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# 如果需要支持http类型的代理则需要指定一个端口
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
# type 默认为 tcp这里需要特别指定为 http
type = http
auth_token = 123
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# 自定义域名在 frps.ini 中配置,方便做统一管理
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# 可选是否加密
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

32
go.mod Normal file
View File

@@ -0,0 +1,32 @@
module github.com/fatedier/frp
go 1.12
require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.9.1 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
github.com/pkg/errors v0.8.0 // indirect
github.com/rakyll/statik v0.1.1
github.com/rodaine/table v1.0.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/testify v1.3.0
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/text v0.3.2 // indirect
)

63
go.sum Normal file
View File

@@ -0,0 +1,63 @@
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 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -23,87 +23,165 @@ import (
ini "github.com/vaughan0/go-ini"
)
var ClientCommonCfg *ClientCommonConf
// client common config
// ClientCommonConf contains information for a client service. It is
// recommended to use GetDefaultClientConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values.
type ClientCommonConf struct {
ConfigFile string
ServerAddr string
ServerPort int64
HttpProxy string
LogFile string
LogWay string
LogLevel string
LogMaxDays int64
PrivilegeToken string
AdminAddr string
AdminPort int64
AdminUser string
AdminPwd string
PoolCount int
TcpMux bool
User string
LoginFailExit bool
Start map[string]struct{}
Protocol string
HeartBeatInterval int64
HeartBeatTimeout int64
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
ServerAddr string `json:"server_addr"`
// ServerPort specifies the port to connect to the server on. By default,
// this value is 7000.
ServerPort int `json:"server_port"`
// HttpProxy specifies a proxy address to connect to the server through. If
// this value is "", the server will be connected to directly. By default,
// this value is read from the "http_proxy" environment variable.
HttpProxy string `json:"http_proxy"`
// LogFile specifies a file where logs will be written to. This value will
// only be used if LogWay is set appropriately. By default, this value is
// "console".
LogFile string `json:"log_file"`
// LogWay specifies the way logging is managed. Valid values are "console"
// or "file". If "console" is used, logs will be printed to stdout. If
// "file" is used, logs will be printed to LogFile. By default, this value
// is "console".
LogWay string `json:"log_way"`
// LogLevel specifies the minimum log level. Valid values are "trace",
// "debug", "info", "warn", and "error". By default, this value is "info".
LogLevel string `json:"log_level"`
// LogMaxDays specifies the maximum number of days to store log information
// before deletion. This is only used if LogWay == "file". By default, this
// value is 0.
LogMaxDays int64 `json:"log_max_days"`
// DisableLogColor disables log colors when LogWay == "console" when set to
// true. By default, this value is false.
DisableLogColor bool `json:"disable_log_color"`
// Token specifies the authorization token used to create keys to be sent
// to the server. The server must have a matching token for authorization
// to succeed. By default, this value is "".
Token string `json:"token"`
// AdminAddr specifies the address that the admin server binds to. By
// default, this value is "127.0.0.1".
AdminAddr string `json:"admin_addr"`
// AdminPort specifies the port for the admin server to listen on. If this
// value is 0, the admin server will not be started. By default, this value
// is 0.
AdminPort int `json:"admin_port"`
// AdminUser specifies the username that the admin server will use for
// login. By default, this value is "admin".
AdminUser string `json:"admin_user"`
// AdminPwd specifies the password that the admin server will use for
// login. By default, this value is "admin".
AdminPwd string `json:"admin_pwd"`
// AssetsDir specifies the local directory that the admin server will load
// resources from. If this value is "", assets will be loaded from the
// bundled executable using statik. By default, this value is "".
AssetsDir string `json:"assets_dir"`
// PoolCount specifies the number of connections the client will make to
// the server in advance. By default, this value is 0.
PoolCount int `json:"pool_count"`
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
// from a client to share a single TCP connection. If this value is true,
// the server must have TCP multiplexing enabled as well. By default, this
// value is true.
TcpMux bool `json:"tcp_mux"`
// User specifies a prefix for proxy names to distinguish them from other
// clients. If this value is not "", proxy names will automatically be
// changed to "{user}.{proxy_name}". By default, this value is "".
User string `json:"user"`
// DnsServer specifies a DNS server address for FRPC to use. If this value
// is "", the default DNS will be used. By default, this value is "".
DnsServer string `json:"dns_server"`
// LoginFailExit controls whether or not the client should exit after a
// failed login attempt. If false, the client will retry until a login
// attempt succeeds. By default, this value is true.
LoginFailExit bool `json:"login_fail_exit"`
// Start specifies a set of enabled proxies by name. If this set is empty,
// all supplied proxies are enabled. By default, this value is an empty
// set.
Start map[string]struct{} `json:"start"`
// Protocol specifies the protocol to use when interacting with the server.
// Valid values are "tcp", "kcp", and "websocket". By default, this value
// is "tcp".
Protocol string `json:"protocol"`
// TLSEnable specifies whether or not TLS should be used when communicating
// with the server.
TLSEnable bool `json:"tls_enable"`
// HeartBeatInterval specifies at what interval heartbeats are sent to the
// server, in seconds. It is not recommended to change this value. By
// default, this value is 30.
HeartBeatInterval int64 `json:"heartbeat_interval"`
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
// before the connection is terminated, in seconds. It is not recommended
// to change this value. By default, this value is 90.
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
}
func GetDeaultClientCommonConf() *ClientCommonConf {
return &ClientCommonConf{
ConfigFile: "./frpc.ini",
// GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ServerAddr: "0.0.0.0",
ServerPort: 7000,
HttpProxy: "",
HttpProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeToken: "",
DisableLogColor: false,
Token: "",
AdminAddr: "127.0.0.1",
AdminPort: 0,
AdminUser: "",
AdminPwd: "",
AssetsDir: "",
PoolCount: 1,
TcpMux: true,
User: "",
DnsServer: "",
LoginFailExit: true,
Start: make(map[string]struct{}),
Protocol: "tcp",
TLSEnable: false,
HeartBeatInterval: 30,
HeartBeatTimeout: 90,
}
}
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
cfg = GetDefaultClientConf()
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
}
var (
tmpStr string
ok bool
v int64
)
cfg = GetDeaultClientCommonConf()
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
if tmpStr, ok = conf.Get("common", "server_addr"); ok {
cfg.ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
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)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
cfg.DisableLogColor = true
}
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
cfg.HttpProxy = tmpStr
} else {
// get http_proxy from env
cfg.HttpProxy = os.Getenv("http_proxy")
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
if tmpStr, ok = conf.Get("common", "log_file"); ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
@@ -112,120 +190,121 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
if tmpStr, ok = conf.Get("common", "log_level"); ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
cfg.PrivilegeToken = tmpStr
if tmpStr, ok = conf.Get("common", "token"); ok {
cfg.Token = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_addr")
if ok {
if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
cfg.AdminAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_port")
if ok {
if tmpStr, ok = conf.Get("common", "admin_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.AdminPort = v
cfg.AdminPort = int(v)
} else {
err = fmt.Errorf("Parse conf error: invalid admin_port")
return
}
}
tmpStr, ok = conf.Get("common", "admin_user")
if ok {
if tmpStr, ok = conf.Get("common", "admin_user"); ok {
cfg.AdminUser = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_pwd")
if ok {
if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
cfg.AdminPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
cfg.PoolCount = 1
} else {
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
cfg.AssetsDir = tmpStr
}
if tmpStr, ok = conf.Get("common", "pool_count"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.PoolCount = int(v)
}
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "user")
if ok {
if tmpStr, ok = conf.Get("common", "user"); ok {
cfg.User = tmpStr
}
tmpStr, ok = conf.Get("common", "start")
if ok {
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{}{}
}
}
tmpStr, ok = conf.Get("common", "login_fail_exit")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
cfg.LoginFailExit = false
} else {
cfg.LoginFailExit = true
}
tmpStr, ok = conf.Get("common", "protocol")
if ok {
// Now it only support tcp and kcp.
if tmpStr != "kcp" {
tmpStr = "tcp"
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
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
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
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
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: heartbeat_interval is incorrect")
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
return
}
return

File diff suppressed because it is too large Load Diff

View File

@@ -19,163 +19,272 @@ import (
"strconv"
"strings"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/utils/util"
)
var ServerCommonCfg *ServerCommonConf
// common config
// ServerCommonConf contains information for a server service. It is
// recommended to use GetDefaultServerConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values.
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
KcpBindPort int64
ProxyBindAddr string
// BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0".
BindAddr string `json:"bind_addr"`
// BindPort specifies the port that the server listens on. By default, this
// value is 7000.
BindPort int `json:"bind_port"`
// BindUdpPort specifies the UDP port that the server listens on. If this
// value is 0, the server will not listen for UDP connections. By default,
// this value is 0
BindUdpPort int `json:"bind_udp_port"`
// BindKcpPort specifies the KCP port that the server listens on. If this
// value is 0, the server will not listen for KCP connections. By default,
// this value is 0.
KcpBindPort int `json:"kcp_bind_port"`
// ProxyBindAddr specifies the address that the proxy binds to. This value
// may be the same as BindAddr. By default, this value is "0.0.0.0".
ProxyBindAddr string `json:"proxy_bind_addr"`
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
// VhostHttpPort specifies the port that the server listens for HTTP Vhost
// requests. If this value is 0, the server will not listen for HTTP
// requests. By default, this value is 0.
VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int64
// VhostHttpsPort specifies the port that the server listens for HTTPS
// Vhost requests. If this value is 0, the server will not listen for HTTPS
// requests. By default, this value is 0.
VhostHttpsPort int `json:"vhost_https_port"`
// if DashboardPort equals 0, dashboard is not available
DashboardPort int64
DashboardUser string
DashboardPwd string
AssetsDir string
LogFile string
LogWay string // console or file
LogLevel string
LogMaxDays int64
PrivilegeMode bool
PrivilegeToken string
AuthTimeout int64
SubDomainHost string
TcpMux bool
// VhostHttpTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60.
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts [][2]int64
MaxPoolCount int64
HeartBeatTimeout int64
UserConnTimeout int64
// DashboardAddr specifies the address that the dashboard binds to. By
// default, this value is "0.0.0.0".
DashboardAddr string `json:"dashboard_addr"`
// DashboardPort specifies the port that the dashboard listens on. If this
// value is 0, the dashboard will not be started. By default, this value is
// 0.
DashboardPort int `json:"dashboard_port"`
// DashboardUser specifies the username that the dashboard will use for
// login. By default, this value is "admin".
DashboardUser string `json:"dashboard_user"`
// DashboardUser specifies the password that the dashboard will use for
// login. By default, this value is "admin".
DashboardPwd string `json:"dashboard_pwd"`
// AssetsDir specifies the local directory that the dashboard will load
// resources from. If this value is "", assets will be loaded from the
// bundled executable using statik. By default, this value is "".
AssetsDir string `json:"asserts_dir"`
// LogFile specifies a file where logs will be written to. This value will
// only be used if LogWay is set appropriately. By default, this value is
// "console".
LogFile string `json:"log_file"`
// LogWay specifies the way logging is managed. Valid values are "console"
// or "file". If "console" is used, logs will be printed to stdout. If
// "file" is used, logs will be printed to LogFile. By default, this value
// is "console".
LogWay string `json:"log_way"`
// LogLevel specifies the minimum log level. Valid values are "trace",
// "debug", "info", "warn", and "error". By default, this value is "info".
LogLevel string `json:"log_level"`
// LogMaxDays specifies the maximum number of days to store log information
// before deletion. This is only used if LogWay == "file". By default, this
// value is 0.
LogMaxDays int64 `json:"log_max_days"`
// DisableLogColor disables log colors when LogWay == "console" when set to
// true. By default, this value is false.
DisableLogColor bool `json:"disable_log_color"`
// Token specifies the authorization token used to authenticate keys
// received from clients. Clients must have a matching token to be
// authorized to use the server. By default, this value is "".
Token string `json:"token"`
// SubDomainHost specifies the domain that will be attached to sub-domains
// requested by the client when using Vhost proxying. For example, if this
// value is set to "frps.com" and the client requested the subdomain
// "test", the resulting URL would be "test.frps.com". By default, this
// value is "".
SubDomainHost string `json:"subdomain_host"`
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
// from a client to share a single TCP connection. By default, this value
// is true.
TcpMux bool `json:"tcp_mux"`
// Custom404Page specifies a path to a custom 404 page to display. If this
// value is "", a default page will be displayed. By default, this value is
// "".
Custom404Page string `json:"custom_404_page"`
// AllowPorts specifies a set of ports that clients are able to proxy to.
// If the length of this value is 0, all ports are allowed. By default,
// this value is an empty set.
AllowPorts map[int]struct{}
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
// this value is 5.
MaxPoolCount int64 `json:"max_pool_count"`
// MaxPortsPerClient specifies the maximum number of ports a single client
// may proxy to. If this value is 0, no limit will be applied. By default,
// this value is 0.
MaxPortsPerClient int64 `json:"max_ports_per_client"`
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
// before terminating the connection. It is not recommended to change this
// value. By default, this value is 90.
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
// UserConnTimeout specifies the maximum time to wait for a work
// connection. By default, this value is 10.
UserConnTimeout int64 `json:"user_conn_timeout"`
}
func GetDefaultServerCommonConf() *ServerCommonConf {
return &ServerCommonConf{
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeMode: true,
PrivilegeToken: "",
AuthTimeout: 900,
SubDomainHost: "",
TcpMux: true,
MaxPoolCount: 5,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
// GetDefaultServerConf returns a server configuration with reasonable
// defaults.
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,
DisableLogColor: false,
Token: "",
SubDomainHost: "",
TcpMux: true,
AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
MaxPortsPerClient: 0,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
Custom404Page: "",
}
}
// Load server common configure.
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
// UnmarshalServerConfFromIni parses the contents of a server configuration ini
// file and returns the resulting server configuration.
func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
cfg = GetDefaultServerConf()
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err)
return ServerCommonConf{}, err
}
var (
tmpStr string
ok bool
v int64
)
cfg = GetDefaultServerCommonConf()
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
cfg.BindAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.BindPort = v
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)
}
}
tmpStr, ok = conf.Get("common", "kcp_bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v > 0 {
cfg.KcpBindPort = v
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)
}
}
tmpStr, ok = conf.Get("common", "proxy_bind_addr")
if ok {
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
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
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
}
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
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
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
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
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
cfg.DashboardUser = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
cfg.DashboardPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
cfg.AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
if tmpStr, ok = conf.Get("common", "log_file"); ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
@@ -184,74 +293,77 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
if tmpStr, ok = conf.Get("common", "log_level"); ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_mode")
if ok {
if tmpStr == "true" {
cfg.PrivilegeMode = true
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
cfg.DisableLogColor = true
}
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{}{}
}
}
// PrivilegeMode configure
if cfg.PrivilegeMode == true {
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
// TODO: check if conflicts exist in port ranges
if ok {
cfg.PrivilegeAllowPorts, err = util.GetPortRanges(allowPortsStr)
if err != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
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
}
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
cfg.MaxPoolCount = v
}
}
tmpStr, ok = conf.Get("common", "authentication_timeout")
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
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 {
cfg.AuthTimeout = v
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
}
cfg.MaxPortsPerClient = v
}
}
tmpStr, ok = conf.Get("common", "subdomain_host")
if ok {
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
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")
@@ -262,3 +374,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
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
}

View File

@@ -28,4 +28,5 @@ var (
HttpProxy string = "http"
HttpsProxy string = "https"
StcpProxy string = "stcp"
XtcpProxy string = "xtcp"
)

View File

@@ -14,8 +14,11 @@
package errors
import "errors"
import (
"errors"
)
var (
ErrMsgType = errors.New("message type error")
ErrMsgType = errors.New("message type error")
ErrCtlClosed = errors.New("control is closed")
)

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,40 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package pool
package msg
import (
"testing"
"io"
"github.com/stretchr/testify/assert"
jsonMsg "github.com/fatedier/golib/msg/json"
)
func TestPutBuf(t *testing.T) {
buf := make([]byte, 512)
PutBuf(buf)
type Message = jsonMsg.Message
buf = make([]byte, 1025)
PutBuf(buf)
var (
msgCtl *jsonMsg.MsgCtl
)
buf = make([]byte, 2*1025)
PutBuf(buf)
buf = make([]byte, 5*1025)
PutBuf(buf)
func init() {
msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
msgCtl.RegisterMsg(typeByte, msg)
}
}
func TestGetBuf(t *testing.T) {
assert := assert.New(t)
buf := GetBuf(200)
assert.Len(buf, 200)
buf = GetBuf(1025)
assert.Len(buf, 1025)
buf = GetBuf(2 * 1024)
assert.Len(buf, 2*1024)
buf = GetBuf(5 * 2000)
assert.Len(buf, 5*2000)
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)
}

View File

@@ -14,57 +14,51 @@
package msg
import (
"net"
"reflect"
)
import "net"
const (
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeCloseProxy = 'c'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypeNewVistorConn = 'v'
TypeNewVistorConnResp = '3'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
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 (
TypeMap map[byte]reflect.Type
TypeStringMap map[reflect.Type]byte
)
func init() {
TypeMap = make(map[byte]reflect.Type)
TypeStringMap = make(map[reflect.Type]byte)
TypeMap[TypeLogin] = reflect.TypeOf(Login{})
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
TypeMap[TypeCloseProxy] = reflect.TypeOf(CloseProxy{})
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
TypeMap[TypeNewVistorConn] = reflect.TypeOf(NewVistorConn{})
TypeMap[TypeNewVistorConnResp] = reflect.TypeOf(NewVistorConnResp{})
TypeMap[TypePing] = reflect.TypeOf(Ping{})
TypeMap[TypePong] = reflect.TypeOf(Pong{})
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
for k, v := range TypeMap {
TypeStringMap[v] = k
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{},
}
}
// Message wraps socket packages for communicating between frpc and frps.
type Message interface{}
)
// When frpc start, client send this message to login to server.
type Login struct {
@@ -82,9 +76,10 @@ type Login struct {
}
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
Error string `json:"error"`
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.
@@ -93,25 +88,29 @@ type NewProxy struct {
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 int64 `json:"remote_port"`
RemotePort int `json:"remote_port"`
// http and https only
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
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"`
Error string `json:"error"`
ProxyName string `json:"proxy_name"`
RemoteAddr string `json:"remote_addr"`
Error string `json:"error"`
}
type CloseProxy struct {
@@ -127,9 +126,13 @@ 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 NewVistorConn struct {
type NewVisitorConn struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
@@ -137,7 +140,7 @@ type NewVistorConn struct {
UseCompression bool `json:"use_compression"`
}
type NewVistorConnResp struct {
type NewVisitorConnResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
}
@@ -153,3 +156,28 @@ type UdpPacket struct {
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"`
}

View File

@@ -1,87 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/utils/errors"
)
type TestStruct struct{}
func TestPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
buffer []byte
err error
)
// error type
msg = &TestStruct{}
buffer, err = Pack(msg)
assert.Error(err, errors.ErrMsgType.Error())
// correct
msg = &Ping{}
buffer, err = Pack(msg)
assert.NoError(err)
b := bytes.NewBuffer(nil)
b.WriteByte(TypePing)
binary.Write(b, binary.BigEndian, int64(2))
b.WriteString("{}")
assert.True(bytes.Equal(b.Bytes(), buffer))
}
func TestUnPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
err error
)
// error message type
msg, err = UnPack('-', []byte("{}"))
assert.Error(err)
// correct
msg, err = UnPack(TypePong, []byte("{}"))
assert.NoError(err)
assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{}))
}
func TestUnPackInto(t *testing.T) {
assert := assert.New(t)
var err error
// correct type
pongMsg := &Pong{}
err = UnPackInto([]byte("{}"), pongMsg)
assert.NoError(err)
// wrong type
loginMsg := &Login{}
err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
assert.Error(err)
}

View File

@@ -1,97 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProcess(t *testing.T) {
assert := assert.New(t)
var (
msg Message
resMsg Message
err error
)
// empty struct
msg = &Ping{}
buffer := bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypePing])
// normal message
msg = &StartWorkConn{
ProxyName: "test",
}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypeStartWorkConn])
startWorkConnMsg, ok := resMsg.(*StartWorkConn)
assert.True(ok)
assert.Equal("test", startWorkConnMsg.ProxyName)
// ReadMsgInto correct
msg = &Pong{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
err = ReadMsgInto(buffer, msg)
assert.NoError(err)
// ReadMsgInto error type
content := []byte(`{"run_id": 123}`)
buffer = bytes.NewBuffer(nil)
buffer.WriteByte(TypeNewWorkConn)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
resMsg = &NewWorkConn{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// message format error
buffer = bytes.NewBuffer([]byte("1234"))
resMsg = &NewProxyResp{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// MaxLength, real message length is 2
MaxMsgLength = 1
msg = &Ping{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
_, err = ReadMsg(buffer)
assert.Error(err)
return
}

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
}

View File

@@ -17,16 +17,15 @@ package plugin
import (
"bufio"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"github.com/fatedier/frp/utils/errors"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
frpIo "github.com/fatedier/golib/io"
gnet "github.com/fatedier/golib/net"
)
const PluginHttpProxy = "http_proxy"
@@ -35,47 +34,6 @@ func init() {
Register(PluginHttpProxy, NewHttpProxyPlugin)
}
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)
}
type HttpProxy struct {
l *Listener
s *http.Server
@@ -106,23 +64,25 @@ func (hp *HttpProxy) Name() string {
return PluginHttpProxy
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) {
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := frpNet.NewShareConn(wrapConn)
request, err := http.ReadRequest(bufio.NewReader(rd))
sc, rd := gnet.NewSharedConn(wrapConn)
firstBytes := make([]byte, 7)
_, err := rd.Read(firstBytes)
if err != nil {
wrapConn.Close()
return
}
if request.Method == http.MethodConnect {
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil))
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
}

129
models/plugin/https2http.go Normal file
View File

@@ -0,0 +1,129 @@
// 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"
"strings"
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
headers map[string]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"]
headers := make(map[string]string)
for k, v := range params {
if !strings.HasPrefix(k, "plugin_header_") {
continue
}
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
headers[k] = v
}
}
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,
headers: headers,
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
}
for k, v := range p.headers {
req.Header.Set(k, v)
}
},
}
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
}

View File

@@ -17,6 +17,12 @@ 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.
@@ -40,6 +46,47 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
type Plugin interface {
Name() string
Handle(conn io.ReadWriteCloser)
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)
}

View File

@@ -32,27 +32,30 @@ func init() {
type Socks5Plugin struct {
Server *gosocks5.Server
user string
passwd string
}
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(&gosocks5.Config{
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) {
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
defer conn.Close()
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
}
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.Server.ServeConn(wrapConn)
}

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

@@ -19,7 +19,9 @@ import (
"io"
"net"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
frpIo "github.com/fatedier/golib/io"
)
const PluginUnixDomainSocket = "unix_domain_socket"
@@ -51,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
return
}
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser) {
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)
}

View File

@@ -21,8 +21,9 @@ import (
"time"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/golib/errors"
"github.com/fatedier/golib/pool"
)
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
@@ -66,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
default:
}
}
return
}
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
@@ -82,6 +82,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
mu.Lock()
delete(udpConnMap, addr)
mu.Unlock()
udpConn.Close()
}()
buf := pool.GetBuf(1500)

View File

@@ -14,8 +14,8 @@ make -f ./Makefile.cross-compiles
rm -rf ./packages
mkdir ./packages
os_all='linux windows darwin'
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
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
@@ -44,7 +44,7 @@ for os in $os_all; do
mv ./frps_${os}_${arch} ${frp_path}/frps
fi
cp ./LICENSE ${frp_path}
cp ./conf/* ${frp_path}
cp -rf ./conf/* ${frp_path}
# packages
cd ./packages

View File

@@ -17,22 +17,75 @@ package server
import (
"fmt"
"io"
"runtime/debug"
"sync"
"time"
"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/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"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/shutdown"
"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 {
// frps service
svr *Service
// 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
@@ -50,11 +103,14 @@ type Control struct {
workConnCh chan net.Conn
// proxies in one client
proxies map[string]Proxy
proxies map[string]proxy.Proxy
// pool count
poolCount int
// ports used, for limitations
portsUsedNum int
// last time got the Ping message
lastPing time.Time
@@ -72,18 +128,31 @@ type Control struct {
allShutdown *shutdown.Shutdown
mu sync.RWMutex
// Server configuration information
serverCfg config.ServerCommonConf
}
func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
serverCfg config.ServerCommonConf) *Control {
poolCount := loginMsg.PoolCount
if poolCount > int(serverCfg.MaxPoolCount) {
poolCount = int(serverCfg.MaxPoolCount)
}
return &Control{
svr: svr,
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),
poolCount: loginMsg.PoolCount,
workConnCh: make(chan net.Conn, poolCount+10),
proxies: make(map[string]proxy.Proxy),
poolCount: poolCount,
portsUsedNum: 0,
lastPing: time.Now(),
runId: loginMsg.RunId,
status: consts.Working,
@@ -91,15 +160,17 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
writerShutdown: shutdown.New(),
managerShutdown: shutdown.New(),
allShutdown: shutdown.New(),
serverCfg: serverCfg,
}
}
// Start send a login success message to client and start working.
func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
Error: "",
Version: version.Full(),
RunId: ctl.runId,
ServerUdpPort: ctl.serverCfg.BindUdpPort,
Error: "",
}
msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -117,6 +188,7 @@ 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()))
}
}()
@@ -137,6 +209,7 @@ 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()))
}
}()
@@ -145,7 +218,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
err = frpErr.ErrCtlClosed
return
}
ctl.conn.Debug("get work connection from pool")
@@ -162,12 +235,12 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
err = frpErr.ErrCtlClosed
ctl.conn.Warn("no work connections avaiable, %v", err)
return
}
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err)
return
@@ -191,13 +264,14 @@ 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(config.ServerCommonCfg.PrivilegeToken))
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start()
@@ -220,13 +294,14 @@ 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(config.ServerCommonCfg.PrivilegeToken))
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
@@ -234,6 +309,7 @@ func (ctl *Control) reader() {
return
} else {
ctl.conn.Warn("read error: %v", err)
ctl.conn.Close()
return
}
} else {
@@ -246,43 +322,54 @@ 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.WaitDown()
ctl.managerShutdown.WaitDone()
close(ctl.sendCh)
ctl.writerShutdown.WaitDown()
ctl.writerShutdown.WaitDone()
ctl.conn.Close()
ctl.readerShutdown.WaitDown()
ctl.readerShutdown.WaitDone()
ctl.mu.Lock()
defer ctl.mu.Unlock()
close(ctl.workConnCh)
for workConn := range ctl.workConnCh {
workConn.Close()
}
ctl.mu.Lock()
defer ctl.mu.Unlock()
for _, pxy := range ctl.proxies {
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
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")
StatsCloseClient()
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()))
}
}()
@@ -295,9 +382,9 @@ func (ctl *Control) manager() {
for {
select {
case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start()
return
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
@@ -307,7 +394,7 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.NewProxy:
// register proxy in this control
err := ctl.RegisterProxy(m)
remoteAddr, err := ctl.RegisterProxy(m)
resp := &msg.NewProxyResp{
ProxyName: m.ProxyName,
}
@@ -315,8 +402,12 @@ func (ctl *Control) manager() {
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)
StatsNewProxy(m.ProxyName, m.ProxyType)
ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
Name: m.ProxyName,
ProxyType: m.ProxyType,
})
}
ctl.sendCh <- resp
case *msg.CloseProxy:
@@ -331,24 +422,44 @@ func (ctl *Control) manager() {
}
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
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.NewProxyConf(pxyMsg)
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
if err != nil {
return err
return
}
// NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := NewProxy(ctl, pxyConf)
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
if err != nil {
return err
return remoteAddr, err
}
err = pxy.Run()
// Check ports used number in each client
if ctl.serverCfg.MaxPortsPerClient > 0 {
ctl.mu.Lock()
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.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 err
return
}
defer func() {
if err != nil {
@@ -356,29 +467,36 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
}
}()
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
err = ctl.pxyManager.Add(pxyMsg.ProxyName, pxy)
if err != nil {
return err
return
}
ctl.mu.Lock()
ctl.proxies[pxy.GetName()] = pxy
ctl.mu.Unlock()
return nil
return
}
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
pxy, ok := ctl.proxies[closeMsg.ProxyName]
if !ok {
ctl.mu.Unlock()
return
}
if ctl.serverCfg.MaxPortsPerClient > 0 {
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
}
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
ctl.pxyManager.Del(pxy.GetName())
delete(ctl.proxies, closeMsg.ProxyName)
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
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)
}

View File

@@ -21,10 +21,9 @@ import (
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
"github.com/gorilla/mux"
)
var (
@@ -32,28 +31,26 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func RunDashboardServer(addr string, port int64) (err error) {
func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
// url router
router := httprouter.New()
router := mux.NewRouter()
user, passwd := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd))
router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd))
router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd))
router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))
router.GET("/api/proxy/https", frpNet.HttprouterBasicAuth(apiProxyHttps, user, passwd))
router.GET("/api/proxy/traffic/:name", frpNet.HttprouterBasicAuth(apiProxyTraffic, user, passwd))
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.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler(
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
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.HandlerFunc("GET", "/", frpNet.HttpBasicAuth(func(w http.ResponseWriter, r *http.Request) {
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}, user, passwd))
})
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{

View File

@@ -23,25 +23,25 @@ import (
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
"github.com/julienschmidt/httprouter"
"github.com/gorilla/mux"
)
type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Code int
Msg string
}
// api/serverinfo
type ServerInfoResp struct {
GeneralResponse
Version string `json:"version"`
VhostHttpPort int64 `json:"vhost_http_port"`
VhostHttpsPort int64 `json:"vhost_https_port"`
AuthTimeout int64 `json:"auth_timeout"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
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"`
@@ -50,26 +50,30 @@ type ServerInfoResp struct {
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
}
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res ServerInfoResp
)
// api/serverinfo
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
defer func() {
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
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: [/api/serverinfo]")
cfg := config.ServerCommonCfg
serverStats := StatsGetServer()
res = ServerInfoResp{
Version: version.Full(),
VhostHttpPort: cfg.VhostHttpPort,
VhostHttpsPort: cfg.VhostHttpsPort,
AuthTimeout: cfg.AuthTimeout,
SubdomainHost: cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount,
HeartBeatTimeout: cfg.HeartBeatTimeout,
log.Info("Http request: [%s]", r.URL.Path)
serverStats := svr.statsCollector.GetServer()
svrResp := ServerInfoResp{
Version: version.Full(),
BindPort: svr.cfg.BindPort,
BindUdpPort: svr.cfg.BindUdpPort,
VhostHttpPort: svr.cfg.VhostHttpPort,
VhostHttpsPort: svr.cfg.VhostHttpsPort,
KcpBindPort: svr.cfg.KcpBindPort,
SubdomainHost: svr.cfg.SubDomainHost,
MaxPoolCount: svr.cfg.MaxPoolCount,
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,
@@ -78,102 +82,117 @@ func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
ProxyTypeCounts: serverStats.ProxyTypeCounts,
}
buf, _ = json.Marshal(&res)
w.Write(buf)
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 config.ProxyConf `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"`
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 {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"`
}
// api/proxy/tcp
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
// 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 [/api/proxy/tcp]: code [%d]", res.Code)
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: [/api/proxy/tcp]")
log.Info("Http request: [%s]", r.URL.Path)
res.Proxies = getProxyStatsByType(consts.TcpProxy)
proxyInfoResp := GetProxyInfoResp{}
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
buf, _ = json.Marshal(&res)
w.Write(buf)
buf, _ := json.Marshal(&proxyInfoResp)
res.Msg = string(buf)
}
// api/proxy/udp
func apiProxyUdp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/udp]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/udp]")
res.Proxies = getProxyStatsByType(consts.UdpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/http
func apiProxyHttp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/http]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/http]")
res.Proxies = getProxyStatsByType(consts.HttpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/https
func apiProxyHttps(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/https]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/https]")
res.Proxies = getProxyStatsByType(consts.HttpsProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
proxyStats := StatsGetProxiesByType(proxyType)
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 := ServerService.pxyManager.GetByName(ps.Name); ok {
proxyInfo.Conf = pxy.GetConf()
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
@@ -189,37 +208,115 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
return
}
// api/proxy/traffic/:name
type GetProxyTrafficResp struct {
GeneralResponse
// 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 apiProxyTraffic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyTrafficResp
)
name := params.ByName("name")
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 [/api/proxy/traffic/:name]: code [%d]", res.Code)
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: [/api/proxy/traffic/:name]")
log.Info("Http request: [%s]", r.URL.Path)
trafficResp := GetProxyTrafficResp{}
trafficResp.Name = name
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
res.Name = name
proxyTrafficInfo := StatsGetProxyTraffic(name)
if proxyTrafficInfo == nil {
res.Code = 1
res.Code = 404
res.Msg = "no proxy info found"
return
} else {
res.TrafficIn = proxyTrafficInfo.TrafficIn
res.TrafficOut = proxyTrafficInfo.TrafficOut
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
}
buf, _ = json.Marshal(&res)
w.Write(buf)
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
}

View File

@@ -1,163 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"io"
"sync"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
)
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
}
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
cm.mu.RLock()
defer cm.mu.RUnlock()
ctl, ok = cm.ctlsByRunId[runId]
return
}
type ProxyManager struct {
// proxies indexed by proxy name
pxys map[string]Proxy
mu sync.RWMutex
}
func NewProxyManager() *ProxyManager {
return &ProxyManager{
pxys: make(map[string]Proxy),
}
}
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, ok := pm.pxys[name]; ok {
return fmt.Errorf("proxy name [%s] is already in use", name)
}
pm.pxys[name] = pxy
return nil
}
func (pm *ProxyManager) Del(name string) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.pxys, name)
}
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
pxy, ok = pm.pxys[name]
return
}
// Manager for vistor listeners.
type VistorManager struct {
vistorListeners map[string]*frpNet.CustomListener
skMap map[string]string
mu sync.RWMutex
}
func NewVistorManager() *VistorManager {
return &VistorManager{
vistorListeners: make(map[string]*frpNet.CustomListener),
skMap: make(map[string]string),
}
}
func (vm *VistorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
vm.mu.Lock()
defer vm.mu.Unlock()
if _, ok := vm.vistorListeners[name]; ok {
err = fmt.Errorf("custom listener for [%s] is repeated", name)
return
}
l = frpNet.NewCustomListener()
vm.vistorListeners[name] = l
vm.skMap[name] = sk
return
}
func (vm *VistorManager) 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.vistorListeners[name]; ok {
var sk string
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
err = fmt.Errorf("vistor 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))
} else {
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
return
}
return
}
func (vm *VistorManager) CloseListener(name string) {
vm.mu.Lock()
defer vm.mu.Unlock()
delete(vm.vistorListeners, name)
delete(vm.skMap, name)
}

View File

@@ -1,285 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/metric"
)
const (
ReserveDays = 7
)
var globalStats *ServerStatistics
type ServerStatistics struct {
TotalTrafficIn metric.DateCounter
TotalTrafficOut metric.DateCounter
CurConns metric.Counter
// counter for clients
ClientCounts metric.Counter
// counter for proxy types
ProxyTypeCounts map[string]metric.Counter
// statistics for different proxies
// key is proxy name
ProxyStatistics map[string]*ProxyStatistics
mu sync.Mutex
}
type ProxyStatistics struct {
Name string
ProxyType string
TrafficIn metric.DateCounter
TrafficOut metric.DateCounter
CurConns metric.Counter
LastStartTime time.Time
LastCloseTime time.Time
}
func init() {
globalStats = &ServerStatistics{
TotalTrafficIn: metric.NewDateCounter(ReserveDays),
TotalTrafficOut: metric.NewDateCounter(ReserveDays),
CurConns: metric.NewCounter(),
ClientCounts: metric.NewCounter(),
ProxyTypeCounts: make(map[string]metric.Counter),
ProxyStatistics: make(map[string]*ProxyStatistics),
}
go func() {
for {
time.Sleep(12 * time.Hour)
log.Debug("start to clear useless proxy statistics data...")
StatsClearUselessInfo()
log.Debug("finish to clear useless proxy statistics data")
}
}()
}
func StatsClearUselessInfo() {
// To check if there are proxies that closed than 7 days and drop them.
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
for name, data := range globalStats.ProxyStatistics {
if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
delete(globalStats.ProxyStatistics, name)
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
}
}
}
func StatsNewClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Inc(1)
}
}
func StatsCloseClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Dec(1)
}
}
func StatsNewProxy(name string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
counter, ok := globalStats.ProxyTypeCounts[proxyType]
if !ok {
counter = metric.NewCounter()
}
counter.Inc(1)
globalStats.ProxyTypeCounts[proxyType] = counter
proxyStats, ok := globalStats.ProxyStatistics[name]
if !(ok && proxyStats.ProxyType == proxyType) {
proxyStats = &ProxyStatistics{
Name: name,
ProxyType: proxyType,
CurConns: metric.NewCounter(),
TrafficIn: metric.NewDateCounter(ReserveDays),
TrafficOut: metric.NewDateCounter(ReserveDays),
}
globalStats.ProxyStatistics[name] = proxyStats
}
proxyStats.LastStartTime = time.Now()
}
}
func StatsCloseProxy(proxyName string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
counter.Dec(1)
}
if proxyStats, ok := globalStats.ProxyStatistics[proxyName]; ok {
proxyStats.LastCloseTime = time.Now()
}
}
}
func StatsOpenConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.CurConns.Inc(1)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.CurConns.Inc(1)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsCloseConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.CurConns.Dec(1)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.CurConns.Dec(1)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsAddTrafficIn(name string, trafficIn int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalTrafficIn.Inc(trafficIn)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.TrafficIn.Inc(trafficIn)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsAddTrafficOut(name string, trafficOut int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalTrafficOut.Inc(trafficOut)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.TrafficOut.Inc(trafficOut)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
// Functions for getting server stats.
type ServerStats struct {
TotalTrafficIn int64
TotalTrafficOut int64
CurConns int64
ClientCounts int64
ProxyTypeCounts map[string]int64
}
func StatsGetServer() *ServerStats {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
s := &ServerStats{
TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(),
TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(),
CurConns: globalStats.CurConns.Count(),
ClientCounts: globalStats.ClientCounts.Count(),
ProxyTypeCounts: make(map[string]int64),
}
for k, v := range globalStats.ProxyTypeCounts {
s.ProxyTypeCounts[k] = v.Count()
}
return s
}
type ProxyStats struct {
Name string
Type string
TodayTrafficIn int64
TodayTrafficOut int64
LastStartTime string
LastCloseTime string
CurConns int64
}
func StatsGetProxiesByType(proxyType string) []*ProxyStats {
res := make([]*ProxyStats, 0)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
for name, proxyStats := range globalStats.ProxyStatistics {
if proxyStats.ProxyType != proxyType {
continue
}
ps := &ProxyStats{
Name: name,
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
}
if !proxyStats.LastStartTime.IsZero() {
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
}
if !proxyStats.LastCloseTime.IsZero() {
ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
}
res = append(res, ps)
}
return res
}
type ProxyTrafficInfo struct {
Name string
TrafficIn []int64
TrafficOut []int64
}
func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
res = &ProxyTrafficInfo{
Name: name,
}
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
}
return
}

180
server/ports/ports.go Normal file
View File

@@ -0,0 +1,180 @@
package ports
import (
"errors"
"fmt"
"net"
"sync"
"time"
)
const (
MinPort = 1
MaxPort = 65535
MaxPortReservedDuration = time.Duration(24) * time.Hour
CleanReservedPortsInterval = time.Hour
)
var (
ErrPortAlreadyUsed = errors.New("port already used")
ErrPortNotAllowed = errors.New("port not allowed")
ErrPortUnAvailable = errors.New("port unavailable")
ErrNoAvailablePort = errors.New("no available port")
)
type PortCtx struct {
ProxyName string
Port int
Closed bool
UpdateTime time.Time
}
type PortManager struct {
reservedPorts map[string]*PortCtx
usedPorts map[int]*PortCtx
freePorts map[int]struct{}
bindAddr string
netType string
mu sync.Mutex
}
func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager {
pm := &PortManager{
reservedPorts: make(map[string]*PortCtx),
usedPorts: make(map[int]*PortCtx),
freePorts: make(map[int]struct{}),
bindAddr: bindAddr,
netType: netType,
}
if len(allowPorts) > 0 {
for port, _ := range allowPorts {
pm.freePorts[port] = struct{}{}
}
} else {
for i := MinPort; i <= MaxPort; i++ {
pm.freePorts[i] = struct{}{}
}
}
go pm.cleanReservedPortsWorker()
return pm
}
func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) {
portCtx := &PortCtx{
ProxyName: name,
Closed: false,
UpdateTime: time.Now(),
}
var ok bool
pm.mu.Lock()
defer func() {
if err == nil {
portCtx.Port = realPort
}
pm.mu.Unlock()
}()
// check reserved ports first
if port == 0 {
if ctx, ok := pm.reservedPorts[name]; ok {
if pm.isPortAvailable(ctx.Port) {
realPort = ctx.Port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
return
}
}
}
if port == 0 {
// get random port
count := 0
maxTryTimes := 5
for k, _ := range pm.freePorts {
count++
if count > maxTryTimes {
break
}
if pm.isPortAvailable(k) {
realPort = k
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
break
}
}
if realPort == 0 {
err = ErrNoAvailablePort
}
} else {
// specified port
if _, ok = pm.freePorts[port]; ok {
if pm.isPortAvailable(port) {
realPort = port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
} else {
err = ErrPortUnAvailable
}
} else {
if _, ok = pm.usedPorts[port]; ok {
err = ErrPortAlreadyUsed
} else {
err = ErrPortNotAllowed
}
}
}
return
}
func (pm *PortManager) isPortAvailable(port int) bool {
if pm.netType == "udp" {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l, err := net.ListenUDP("udp", addr)
if err != nil {
return false
}
l.Close()
return true
} else {
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l.Close()
return true
}
}
func (pm *PortManager) Release(port int) {
pm.mu.Lock()
defer pm.mu.Unlock()
if ctx, ok := pm.usedPorts[port]; ok {
pm.freePorts[port] = struct{}{}
delete(pm.usedPorts, port)
ctx.Closed = true
ctx.UpdateTime = time.Now()
}
}
// Release reserved port if it isn't used in last 24 hours.
func (pm *PortManager) cleanReservedPortsWorker() {
for {
time.Sleep(CleanReservedPortsInterval)
pm.mu.Lock()
for name, ctx := range pm.reservedPorts {
if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration {
delete(pm.reservedPorts, name)
}
}
pm.mu.Unlock()
}
}

View File

@@ -1,514 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp"
"github.com/fatedier/frp/utils/errors"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/vhost"
)
type Proxy interface {
Run() error
GetControl() *Control
GetName() string
GetConf() config.ProxyConf
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
Close()
log.Logger
}
type BaseProxy struct {
name string
ctl *Control
listeners []frpNet.Listener
mu sync.RWMutex
log.Logger
}
func (pxy *BaseProxy) GetName() string {
return pxy.name
}
func (pxy *BaseProxy) GetControl() *Control {
return pxy.ctl
}
func (pxy *BaseProxy) Close() {
pxy.Info("proxy closing")
for _, l := range pxy.listeners {
l.Close()
}
}
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
ctl := pxy.GetControl()
// try all connections from the pool
for i := 0; i < ctl.poolCount+1; i++ {
if workConn, err = ctl.GetWorkConn(); err != nil {
pxy.Warn("failed to get work connection: %v", err)
return
}
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
workConn.AddLogPrefix(pxy.GetName())
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
ProxyName: pxy.GetName(),
})
if err != nil {
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
workConn.Close()
} else {
break
}
}
if err != nil {
pxy.Error("try to get work connection failed in the end")
return
}
return
}
// startListenHandler start a goroutine handler for each listener.
// p: p will just be passed to handler(Proxy, frpNet.Conn).
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn)) {
for _, listener := range pxy.listeners {
go func(l frpNet.Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
if err != nil {
pxy.Info("listener is closed")
return
}
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
go handler(p, c)
}
}(listener)
}
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
basePxy := BaseProxy{
name: pxyConf.GetName(),
ctl: ctl,
listeners: make([]frpNet.Listener, 0),
Logger: log.NewPrefixLogger(ctl.runId),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
pxy.AddLogPrefix(pxy.GetName())
return
}
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
}
func (pxy *TcpProxy) Run() error {
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if err != nil {
return err
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
}
func (pxy *HttpProxy) Run() (err error) {
routeConfig := &vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
}
locations := pxy.cfg.Locations
if len(locations) == 0 {
locations = []string{""}
}
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return
}
func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpsProxy struct {
BaseProxy
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
routeConfig := &vhost.VhostRouteConfig{}
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return
}
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpsProxy) Close() {
pxy.BaseProxy.Close()
}
type StcpProxy struct {
BaseProxy
cfg *config.StcpProxyConf
}
func (pxy *StcpProxy) Run() error {
listener, err := pxy.ctl.svr.vistorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if err != nil {
return err
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
}
func (pxy *StcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *StcpProxy) Close() {
pxy.BaseProxy.Close()
pxy.ctl.svr.vistorManager.CloseListener(pxy.GetName())
}
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
// udpConn is the listener of udp packages
udpConn *net.UDPConn
// there are always only one workConn at the same time
// get another one if it closed
workConn net.Conn
// sendCh is used for sending packages to workConn
sendCh chan *msg.UdpPacket
// readCh is used for reading packages from workConn
readCh chan *msg.UdpPacket
// checkCloseCh is used for watching if workConn is closed
checkCloseCh chan int
isClosed bool
}
func (pxy *UdpProxy) Run() (err error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort))
if err != nil {
return err
}
udpConn, err := net.ListenUDP("udp", addr)
if err != nil {
pxy.Warn("listen udp port error: %v", err)
return err
}
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.udpConn = udpConn
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.checkCloseCh = make(chan int)
// read message from workConn, if it returns any error, notify proxy to start a new workConn
workConnReaderFn := func(conn net.Conn) {
for {
var (
rawMsg msg.Message
errRet error
)
pxy.Trace("loop waiting message from udp workConn")
// client will send heartbeat in workConn for keeping alive
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
pxy.Warn("read from workConn for udp error: %v", errRet)
conn.Close()
// notify proxy to start a new work connection
// ignore error here, it means the proxy is closed
errors.PanicToError(func() {
pxy.checkCloseCh <- 1
})
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Ping:
pxy.Trace("udp work conn get ping message")
continue
case *msg.UdpPacket:
if errRet := errors.PanicToError(func() {
pxy.Trace("get udp message from workConn: %s", m.Content)
pxy.readCh <- m
StatsAddTrafficOut(pxy.GetName(), int64(len(m.Content)))
}); errRet != nil {
conn.Close()
pxy.Info("reader goroutine for udp work connection closed")
return
}
}
}
}
// send message to workConn
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
var errRet error
for {
select {
case udpMsg, ok := <-pxy.sendCh:
if !ok {
pxy.Info("sender goroutine for udp work connection closed")
return
}
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
conn.Close()
return
} else {
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
StatsAddTrafficIn(pxy.GetName(), int64(len(udpMsg.Content)))
continue
}
case <-ctx.Done():
pxy.Info("sender goroutine for udp work connection closed")
return
}
}
}
go func() {
// Sleep a while for waiting control send the NewProxyResp to client.
time.Sleep(500 * time.Millisecond)
for {
workConn, err := pxy.GetWorkConnFromPool()
if err != nil {
time.Sleep(1 * time.Second)
// check if proxy is closed
select {
case _, ok := <-pxy.checkCloseCh:
if !ok {
return
}
default:
}
continue
}
// close the old workConn and replac it with a new one
if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.workConn = workConn
ctx, cancel := context.WithCancel(context.Background())
go workConnReaderFn(workConn)
go workConnSenderFn(workConn, ctx)
_, ok := <-pxy.checkCloseCh
cancel()
if !ok {
return
}
}
}()
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
// Client will transfor udp message to local udp service and waiting for response for a while.
// Response will be wrapped to be forwarded by work connection to server.
// Close readCh and sendCh at the end.
go func() {
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
pxy.Close()
}()
return nil
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *UdpProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
if !pxy.isClosed {
pxy.isClosed = true
pxy.BaseProxy.Close()
if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.udpConn.Close()
// all channels only closed here
close(pxy.checkCloseCh)
close(pxy.readCh)
close(pxy.sendCh)
}
}
// HandleUserTcpConnection is used for incoming tcp user connections.
// It can be used for tcp, http, https type.
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
defer userConn.Close()
// try all connections from the pool
workConn, err := pxy.GetWorkConnFromPool()
if err != nil {
return
}
defer workConn.Close()
var local io.ReadWriteCloser = workConn
cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption {
local, err = frpIo.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
}
}
if cfg.UseCompression {
local = frpIo.WithCompression(local)
}
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
StatsOpenConnection(pxy.GetName())
inCount, outCount := frpIo.Join(local, userConn)
StatsCloseConnection(pxy.GetName())
StatsAddTrafficIn(pxy.GetName(), inCount)
StatsAddTrafficOut(pxy.GetName(), outCount)
pxy.Debug("join connections closed")
}

182
server/proxy/http.go Normal file
View File

@@ -0,0 +1,182 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"io"
"net"
"strings"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server/stats"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost"
frpIo "github.com/fatedier/golib/io"
)
type HttpProxy struct {
*BaseProxy
cfg *config.HttpProxyConf
closeFuncs []func()
}
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
routeConfig := vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Headers: pxy.cfg.Headers,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
CreateConnFn: pxy.GetRealConn,
}
locations := pxy.cfg.Locations
if len(locations) == 0 {
locations = []string{""}
}
defer func() {
if err != nil {
pxy.Close()
}
}()
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
if domain == "" {
continue
}
routeConfig.Domain = domain
for _, location := range locations {
routeConfig.Location = location
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
// handle group
if pxy.cfg.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
})
} else {
// no group
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
}
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
}
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
for _, location := range locations {
routeConfig.Location = location
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
// handle group
if pxy.cfg.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
})
} else {
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
}
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
}
}
remoteAddr = strings.Join(addrs, ",")
return
}
func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
if errRet != nil {
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
}
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
if errRet != nil {
err = errRet
return
}
var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc)
}
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
return
}
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
name := pxy.GetName()
pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name})
pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
ProxyName: name,
TrafficBytes: totalWrite,
})
pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
ProxyName: name,
TrafficBytes: totalRead,
})
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
for _, closeFn := range pxy.closeFuncs {
closeFn()
}
}

80
server/proxy/https.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"strings"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost"
)
type HttpsProxy struct {
*BaseProxy
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
routeConfig := &vhost.VhostRouteConfig{}
defer func() {
if err != nil {
pxy.Close()
}
}()
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
if domain == "" {
continue
}
routeConfig.Domain = domain
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
remoteAddr = strings.Join(addrs, ",")
return
}
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpsProxy) Close() {
pxy.BaseProxy.Close()
}

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