Compare commits

...

129 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
763 changed files with 302096 additions and 7481 deletions

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 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 将会直接关闭。)
(请不要在 issue 评论中出现无意义的 **加1****我也是** 等内容,将会被直接删除。)
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
Use the commands below to provide key information from your environment: 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 You do NOT have to include this information if this is a FEATURE REQUEST

View File

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

View File

@@ -6,11 +6,12 @@ build: frps frpc
# compile assets into binary file # compile assets into binary file
file: file:
rm -rf ./assets/static/* rm -rf ./assets/frps/static/*
cp -rf ./web/frps/dist/* ./assets/static rm -rf ./assets/frpc/static/*
go get -d github.com/rakyll/statik cp -rf ./web/frps/dist/* ./assets/frps/static
go install github.com/rakyll/statik cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/statik rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/... go generate ./assets/...
fmt: fmt:
@@ -18,7 +19,6 @@ fmt:
frps: frps:
go build -o bin/frps ./cmd/frps go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc: frpc:
go build -o bin/frpc ./cmd/frpc go build -o bin/frpc ./cmd/frpc

View File

@@ -22,14 +22,17 @@ Now it also try to support p2p connect.
* [Forward DNS query request](#forward-dns-query-request) * [Forward DNS query request](#forward-dns-query-request)
* [Forward unix domain socket](#forward-unix-domain-socket) * [Forward unix domain socket](#forward-unix-domain-socket)
* [Expose a simple http file server](#expose-a-simple-http-file-server) * [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) * [Expose your service in security](#expose-your-service-in-security)
* [P2P Mode](#p2p-mode) * [P2P Mode](#p2p-mode)
* [Features](#features) * [Features](#features)
* [Configuration File](#configuration-file) * [Configuration File](#configuration-file)
* [Configuration file template](#configuration-file-template) * [Configuration file template](#configuration-file-template)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [Authentication](#authentication) * [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression) * [Encryption and Compression](#encryption-and-compression)
* [TLS](#tls)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Get proxy status from client](#get-proxy-status-from-client) * [Get proxy status from client](#get-proxy-status-from-client)
* [Port White List](#port-white-list) * [Port White List](#port-white-list)
@@ -42,6 +45,8 @@ Now it also try to support p2p connect.
* [Rewriting the Host Header](#rewriting-the-host-header) * [Rewriting the Host Header](#rewriting-the-host-header)
* [Set Headers In HTTP Request](#set-headers-in-http-request) * [Set Headers In HTTP Request](#set-headers-in-http-request)
* [Get Real IP](#get-real-ip) * [Get Real IP](#get-real-ip)
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
* [Proxy Protocol](#proxy-protocol)
* [Password protecting your web service](#password-protecting-your-web-service) * [Password protecting your web service](#password-protecting-your-web-service)
* [Custom subdomain names](#custom-subdomain-names) * [Custom subdomain names](#custom-subdomain-names)
* [URL routing](#url-routing) * [URL routing](#url-routing)
@@ -61,7 +66,7 @@ Now it also try to support p2p connect.
frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing. frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
**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 ## Architecture
@@ -241,11 +246,35 @@ Configure frps same as above.
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`. 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 ### Expose your service in security
For some services, if expose them to the public network directly will be a security risk. 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. Configure frps same as above.
@@ -389,6 +418,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
![dashboard](/doc/pic/dashboard.png) ![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 ### Authentication
`token` in frps.ini and frpc.ini should be same. `token` in frps.ini and frpc.ini should be same.
@@ -407,6 +452,14 @@ use_encryption = true
use_compression = 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 ### 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. First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
@@ -458,8 +511,6 @@ tcp_mux = false
### Support KCP Protocol ### 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. 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: Using kcp in frp:
@@ -510,7 +561,8 @@ This feature is fit for a large number of short connections.
### Load balancing ### Load balancing
Load balancing is supported by `group`. Load balancing is supported by `group`.
This feature is available only for type `tcp` now.
This feature is available only for type `tcp` and `http` now.
```ini ```ini
# frpc.ini # frpc.ini
@@ -533,6 +585,10 @@ group_key = 123
Proxies in same group will accept connections from port 80 randomly. 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
Health check feature can help you achieve high availability with load balancing. Health check feature can help you achieve high availability with load balancing.
@@ -592,7 +648,7 @@ custom_domains = test.yourdomain.com
host_header_rewrite = dev.yourdomain.com host_header_rewrite = dev.yourdomain.com
``` ```
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address. The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
### Set Headers In HTTP Request ### Set Headers In HTTP Request
@@ -613,9 +669,32 @@ In this example, it will set header `X-From-Where: frp` to http request.
### Get Real IP ### Get Real IP
#### HTTP X-Forwarded-For
Features for http proxy only. 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`.
#### Proxy Protocol
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
Here is an example for https service:
```ini
# frpc.ini
[web]
type = https
local_port = 443
custom_domains = test.yourdomain.com
# now v1 and v2 is supported
proxy_protocol_version = v2
```
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
Then you can get it from HTTP request header in your local service.
### Password protecting your web service ### Password protecting your web service
@@ -736,8 +815,6 @@ plugin_http_passwd = abc
## Development Plan ## Development Plan
* Log http request information in frps. * Log http request information in frps.
* Direct reverse proxy, like haproxy.
* kubernetes ingress support.
## Contributing ## Contributing

View File

@@ -16,16 +16,19 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器) * [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务) * [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求) * [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字) * [转发 Unix 域套接字](#转发-unix-域套接字)
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务) * [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
* [安全地暴露内网服务](#安全地暴露内网服务) * [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透) * [点对点内网穿透](#点对点内网穿透)
* [功能说明](#功能说明) * [功能说明](#功能说明)
* [配置文件](#配置文件) * [配置文件](#配置文件)
* [配置文件模版渲染](#配置文件模版渲染) * [配置文件模版渲染](#配置文件模版渲染)
* [Dashboard](#dashboard) * [Dashboard](#dashboard)
* [Admin UI](#admin-ui)
* [身份验证](#身份验证) * [身份验证](#身份验证)
* [加密与压缩](#加密与压缩) * [加密与压缩](#加密与压缩)
* [TLS](#tls)
* [客户端热加载配置文件](#客户端热加载配置文件) * [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态) * [客户端查看代理状态](#客户端查看代理状态)
* [端口白名单](#端口白名单) * [端口白名单](#端口白名单)
@@ -38,6 +41,8 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [修改 Host Header](#修改-host-header) * [修改 Host Header](#修改-host-header)
* [设置 HTTP 请求的 header](#设置-http-请求的-header) * [设置 HTTP 请求的 header](#设置-http-请求的-header)
* [获取用户真实 IP](#获取用户真实-ip) * [获取用户真实 IP](#获取用户真实-ip)
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
* [Proxy Protocol](#proxy-protocol)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务) * [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名) * [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由) * [URL 路由](#url-路由)
@@ -47,6 +52,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [开发计划](#开发计划) * [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献) * [为 frp 做贡献](#为-frp-做贡献)
* [捐助](#捐助) * [捐助](#捐助)
* [知识星球](#知识星球)
* [支付宝扫码捐赠](#支付宝扫码捐赠) * [支付宝扫码捐赠](#支付宝扫码捐赠)
* [微信支付捐赠](#微信支付捐赠) * [微信支付捐赠](#微信支付捐赠)
* [Paypal 捐赠](#paypal-捐赠) * [Paypal 捐赠](#paypal-捐赠)
@@ -123,7 +129,7 @@ master 分支用于发布稳定版本dev 分支用于开发,您可以尝试
vhost_http_port = 8080 vhost_http_port = 8080
``` ```
2. 启动 frps 2. 启动 frps
`./frps -c ./frps.ini` `./frps -c ./frps.ini`
@@ -188,7 +194,7 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
`dig @x.x.x.x -p 6000 www.google.com` `dig @x.x.x.x -p 6000 www.google.com`
### 转发 Unix域套接字 ### 转发 Unix 域套接字
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。 通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
@@ -241,6 +247,34 @@ frps 的部署步骤同上。
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。 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` 即可。
### 安全地暴露内网服务 ### 安全地暴露内网服务
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。 对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
@@ -404,6 +438,24 @@ dashboard_pwd = admin
![dashboard](/doc/pic/dashboard.png) ![dashboard](/doc/pic/dashboard.png)
### Admin UI
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
```ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
```
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI用户名密码默认为 `admin`。
如果想要在外网环境访问 Admin UI将 7400 端口映射出去即可,但需要重视安全风险。
### 身份验证 ### 身份验证
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。 服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
@@ -426,6 +478,14 @@ use_compression = true
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。 如果传输的报文长度较长,通过设置 `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 秒内完成代理的更新。
@@ -485,7 +545,7 @@ tcp_mux = false
### 底层通信可选 kcp 协议 ### 底层通信可选 kcp 协议
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。 底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
开启 kcp 协议支持: 开启 kcp 协议支持:
@@ -537,7 +597,8 @@ tcp_mux = false
### 负载均衡 ### 负载均衡
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。 可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
目前只支持 tcp 类型的 proxy。
目前只支持 TCP 和 HTTP 类型的 proxy。
```ini ```ini
# frpc.ini # frpc.ini
@@ -558,7 +619,9 @@ group_key = 123
用户连接 frps 服务器的 80 端口frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。 用户连接 frps 服务器的 80 端口frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。 TCP 类型代理要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
HTTP 类型代理要求 `group_key, custom_domains 或 subdomain 和 locations` 相同。
### 健康检查 ### 健康检查
@@ -639,7 +702,34 @@ header_X-From-Where = frp
### 获取用户真实 IP ### 获取用户真实 IP
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。 #### HTTP X-Forwarded-For
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP默认启用。
#### Proxy Protocol
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
**Proxy Protocol** 功能启用后frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
这里以 https 类型为例:
```ini
# frpc.ini
[web]
type = https
local_port = 443
custom_domains = test.yourdomain.com
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
proxy_protocol_version = v2
```
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
### 通过密码保护你的 web 服务 ### 通过密码保护你的 web 服务

View File

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

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,7 +20,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/assets"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -35,12 +35,21 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// url router // url router
router := mux.NewRouter() router := mux.NewRouter()
user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware) router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go // api, see dashboard_api.go
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET") router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
router.HandleFunc("/api/status", svr.apiStatus).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) address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{ server := &http.Server{

View File

@@ -17,68 +17,64 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
"github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
// api/reload // GET api/reload
type ReloadResp struct {
GeneralResponse
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ReloadResp log.Info("Http request [/api/reload]")
)
defer func() { defer func() {
log.Info("Http response [/api/reload]: code [%d]", res.Code) log.Info("Http response [/api/reload], code [%d]", res.Code)
buf, _ = json.Marshal(&res) w.WriteHeader(res.Code)
w.Write(buf) if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [/api/reload]") content, err := config.GetRenderedConfFromFile(svr.cfgFile)
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
if err != nil { if err != nil {
res.Code = 1 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err) log.Warn("reload frpc config file error: %s", res.Msg)
return return
} }
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content) newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
if err != nil { if err != nil {
res.Code = 2 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err) log.Warn("reload frpc common section error: %s", res.Msg)
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 3 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
err = svr.ReloadConf(pxyCfgs, visitorCfgs) err = svr.ReloadConf(pxyCfgs, visitorCfgs)
if err != nil { if err != nil {
res.Code = 4 res.Code = 500
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
log.Info("success reload conf") log.Info("success reload conf")
@@ -110,7 +106,7 @@ 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) 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 (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp { func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
psr := ProxyStatusResp{ psr := ProxyStatusResp{
Name: status.Name, Name: status.Name,
Type: status.Type, Type: status.Type,
@@ -124,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
} }
psr.Plugin = cfg.Plugin psr.Plugin = cfg.Plugin
if status.Err != "" { if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
} else { } else {
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr psr.RemoteAddr = serverAddr + status.RemoteAddr
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
if cfg.LocalPort != 0 { if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
} }
if status.Err != "" { if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
} else { } else {
psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr psr.RemoteAddr = serverAddr + status.RemoteAddr
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
if cfg.LocalPort != 0 { if cfg.LocalPort != 0 {
@@ -163,7 +159,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
return psr return psr
} }
// api/status // GET api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
@@ -175,29 +171,29 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
res.Https = make([]ProxyStatusResp, 0) res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0) res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0) res.Xtcp = make([]ProxyStatusResp, 0)
log.Info("Http request [/api/status]")
defer func() { defer func() {
log.Info("Http response [/api/status]") log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res) buf, _ = json.Marshal(&res)
w.Write(buf) w.Write(buf)
}() }()
log.Info("Http request: [/api/status]")
ps := svr.ctl.pm.GetAllProxyStatus() ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps { for _, status := range ps {
switch status.Type { switch status.Type {
case "tcp": case "tcp":
res.Tcp = append(res.Tcp, NewProxyStatusResp(status)) res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "udp": case "udp":
res.Udp = append(res.Udp, NewProxyStatusResp(status)) res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "http": case "http":
res.Http = append(res.Http, NewProxyStatusResp(status)) res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "https": case "https":
res.Https = append(res.Https, NewProxyStatusResp(status)) res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "stcp": case "stcp":
res.Stcp = append(res.Stcp, NewProxyStatusResp(status)) res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "xtcp": case "xtcp":
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status)) res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
} }
} }
sort.Sort(ByProxyStatusResp(res.Tcp)) sort.Sort(ByProxyStatusResp(res.Tcp))
@@ -208,3 +204,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
sort.Sort(ByProxyStatusResp(res.Xtcp)) sort.Sort(ByProxyStatusResp(res.Xtcp))
return 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,6 +15,7 @@
package client package client
import ( import (
"crypto/tls"
"fmt" "fmt"
"io" "io"
"runtime/debug" "runtime/debug"
@@ -22,7 +23,6 @@ import (
"time" "time"
"github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -64,16 +64,24 @@ type Control struct {
// last time got the Pong message // last time got the Pong message
lastPong time.Time lastPong time.Time
// The client configuration
clientCfg config.ClientCommonConf
readerShutdown *shutdown.Shutdown readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown writerShutdown *shutdown.Shutdown
msgHandlerShutdown *shutdown.Shutdown msgHandlerShutdown *shutdown.Shutdown
// The UDP port that the server is listening on
serverUDPPort int
mu sync.RWMutex mu sync.RWMutex
log.Logger log.Logger
} }
func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control { 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{ ctl := &Control{
runId: runId, runId: runId,
conn: conn, conn: conn,
@@ -83,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
readCh: make(chan msg.Message, 100), readCh: make(chan msg.Message, 100),
closedCh: make(chan struct{}), closedCh: make(chan struct{}),
closedDoneCh: make(chan struct{}), closedDoneCh: make(chan struct{}),
clientCfg: clientCfg,
readerShutdown: shutdown.New(), readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(), writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(), msgHandlerShutdown: shutdown.New(),
serverUDPPort: serverUDPPort,
Logger: log.NewPrefixLogger(""), Logger: log.NewPrefixLogger(""),
} }
ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId) ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
ctl.vm = NewVisitorManager(ctl) ctl.vm = NewVisitorManager(ctl)
ctl.vm.Reload(visitorCfgs) ctl.vm.Reload(visitorCfgs)
@@ -130,7 +140,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
workConn.AddLogPrefix(startMsg.ProxyName) workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy // dispatch this work connection to related proxy
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn) ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
} }
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) { func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
@@ -147,6 +157,9 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
func (ctl *Control) Close() error { func (ctl *Control) Close() error {
ctl.pm.Close() ctl.pm.Close()
ctl.conn.Close() ctl.conn.Close()
if ctl.session != nil {
ctl.session.Close()
}
return nil return nil
} }
@@ -157,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
// connectServer return a new connection to frps // connectServer return a new connection to frps
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
if g.GlbClientCfg.TcpMux { if ctl.clientCfg.TcpMux {
stream, errRet := ctl.session.OpenStream() stream, errRet := ctl.session.OpenStream()
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -166,8 +179,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
} }
conn = frpNet.WrapConn(stream) conn = frpNet.WrapConn(stream)
} else { } else {
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, var tlsConfig *tls.Config
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) 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 { if err != nil {
ctl.Warn("start new connection to server error: %v", err) ctl.Warn("start new connection to server error: %v", err)
return return
@@ -187,7 +206,7 @@ func (ctl *Control) reader() {
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
defer close(ctl.closedCh) defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token)) encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
for { for {
if m, err := msg.ReadMsg(encReader); err != nil { if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF { if err == io.EOF {
@@ -195,6 +214,7 @@ func (ctl *Control) reader() {
return return
} else { } else {
ctl.Warn("read error: %v", err) ctl.Warn("read error: %v", err)
ctl.conn.Close()
return return
} }
} else { } else {
@@ -206,7 +226,7 @@ func (ctl *Control) reader() {
// writer writes messages got from sendCh to frps // writer writes messages got from sendCh to frps
func (ctl *Control) writer() { func (ctl *Control) writer() {
defer ctl.writerShutdown.Done() defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token)) encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
if err != nil { if err != nil {
ctl.conn.Error("crypto new writer error: %v", err) ctl.conn.Error("crypto new writer error: %v", err)
ctl.conn.Close() ctl.conn.Close()
@@ -235,7 +255,7 @@ func (ctl *Control) msgHandler() {
}() }()
defer ctl.msgHandlerShutdown.Done() defer ctl.msgHandlerShutdown.Done()
hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second) hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop() defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second) hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop() defer hbCheck.Stop()
@@ -249,7 +269,7 @@ func (ctl *Control) msgHandler() {
ctl.Debug("send heartbeat to server") ctl.Debug("send heartbeat to server")
ctl.sendCh <- &msg.Ping{} ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C: case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second { if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout") ctl.Warn("heartbeat timeout")
// let reader() stop // let reader() stop
ctl.conn.Close() ctl.conn.Close()
@@ -293,6 +313,9 @@ func (ctl *Control) worker() {
ctl.vm.Close() ctl.vm.Close()
close(ctl.closedDoneCh) close(ctl.closedDoneCh)
if ctl.session != nil {
ctl.session.Close()
}
return return
} }
} }

View File

@@ -18,6 +18,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"time" "time"
@@ -94,12 +96,12 @@ func (monitor *HealthCheckMonitor) Stop() {
func (monitor *HealthCheckMonitor) checkWorker() { func (monitor *HealthCheckMonitor) checkWorker() {
for { for {
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout)) doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
err := monitor.doCheck(ctx) err := monitor.doCheck(doCtx)
// check if this monitor has been closed // check if this monitor has been closed
select { select {
case <-ctx.Done(): case <-monitor.ctx.Done():
cancel() cancel()
return return
default: default:
@@ -170,6 +172,8 @@ func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body)
if resp.StatusCode/100 != 2 { if resp.StatusCode/100 != 2 {
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode) return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)

View File

@@ -18,11 +18,13 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/plugin"
@@ -33,6 +35,8 @@ import (
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io" frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool" "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. // Proxy defines how to handle work connections for different proxy type.
@@ -40,45 +44,47 @@ type Proxy interface {
Run() error Run() error
// InWorkConn accept work connections registered to server. // InWorkConn accept work connections registered to server.
InWorkConn(conn frpNet.Conn) InWorkConn(frpNet.Conn, *msg.StartWorkConn)
Close() Close()
log.Logger log.Logger
} }
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) { func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
baseProxy := BaseProxy{ baseProxy := BaseProxy{
Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
clientCfg: clientCfg,
serverUDPPort: serverUDPPort,
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
} }
@@ -86,14 +92,16 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
} }
type BaseProxy struct { type BaseProxy struct {
closed bool closed bool
mu sync.RWMutex mu sync.RWMutex
clientCfg config.ClientCommonConf
serverUDPPort int
log.Logger log.Logger
} }
// TCP // TCP
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -115,14 +123,14 @@ func (pxy *TcpProxy) Close() {
} }
} }
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) { func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(g.GlbClientCfg.Token)) []byte(pxy.clientCfg.Token), m)
} }
// HTTP // HTTP
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -144,14 +152,14 @@ func (pxy *HttpProxy) Close() {
} }
} }
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) { func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(g.GlbClientCfg.Token)) []byte(pxy.clientCfg.Token), m)
} }
// HTTPS // HTTPS
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -173,14 +181,14 @@ func (pxy *HttpsProxy) Close() {
} }
} }
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) { func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(g.GlbClientCfg.Token)) []byte(pxy.clientCfg.Token), m)
} }
// STCP // STCP
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -202,14 +210,14 @@ func (pxy *StcpProxy) Close() {
} }
} }
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) { func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(g.GlbClientCfg.Token)) []byte(pxy.clientCfg.Token), m)
} }
// XTCP // XTCP
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@@ -231,7 +239,7 @@ func (pxy *XtcpProxy) Close() {
} }
} }
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) { func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
defer conn.Close() defer conn.Close()
var natHoleSidMsg msg.NatHoleSid var natHoleSidMsg msg.NatHoleSid
err := msg.ReadMsgInto(conn, &natHoleSidMsg) err := msg.ReadMsgInto(conn, &natHoleSidMsg)
@@ -245,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
Sid: natHoleSidMsg.Sid, Sid: natHoleSidMsg.Sid,
} }
raddr, _ := net.ResolveUDPAddr("udp", raddr, _ := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort)) fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
clientConn, err := net.DialUDP("udp", nil, raddr) clientConn, err := net.DialUDP("udp", nil, raddr)
defer clientConn.Close() defer clientConn.Close()
@@ -278,37 +286,102 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
return return
} }
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
// Send sid to visitor udp address. // Send detect message
time.Sleep(time.Second) 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()) laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr) /*
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 { if err != nil {
pxy.Error("resolve visitor udp address error: %v", err) pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
return return
} }
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
pxy.Trace("send all detect msg done")
lConn, err := net.DialUDP("udp", laddr, daddr) msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
// Listen for clientConn's address and wait for visitor connection
lConn, err := net.ListenUDP("udp", laddr)
if err != nil { if err != nil {
pxy.Error("dial visitor udp address error: %v", err) pxy.Error("listen on visitorConn's local adress error: %v", err)
return return
} }
lConn.Write([]byte(natHoleRespMsg.Sid)) defer lConn.Close()
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr) 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 { if err != nil {
pxy.Error("create kcp connection from udp connection error: %v", err) pxy.Error("create kcp connection from udp connection error: %v", err)
return 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, HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk)) 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 // UDP
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf
@@ -346,7 +419,7 @@ func (pxy *UdpProxy) Close() {
} }
} }
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) { func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
// close resources releated with old workConn // close resources releated with old workConn
pxy.Close() pxy.Close()
@@ -413,7 +486,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
// Common handler for tcp work connections. // Common handler for tcp work connections.
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) { baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
var ( var (
remote io.ReadWriteCloser remote io.ReadWriteCloser
@@ -433,10 +506,43 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
remote = frpIo.WithCompression(remote) 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 proxyPlugin != nil {
// if plugin is set, let plugin handle connections first // if plugin is set, let plugin handle connections first
workConn.Debug("handle by plugin: %s", proxyPlugin.Name()) workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
proxyPlugin.Handle(remote, workConn) proxyPlugin.Handle(remote, workConn, extraInfo)
workConn.Debug("handle by plugin finished") workConn.Debug("handle by plugin finished")
return return
} else { } else {
@@ -449,6 +555,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), 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()) localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
if len(extraInfo) > 0 {
localConn.Write(extraInfo)
}
frpIo.Join(localConn, remote) frpIo.Join(localConn, remote)
workConn.Debug("join connections closed") workConn.Debug("join connections closed")
} }

View File

@@ -20,17 +20,24 @@ type ProxyManager struct {
closed bool closed bool
mu sync.RWMutex mu sync.RWMutex
clientCfg config.ClientCommonConf
// The UDP port that the server is listening on
serverUDPPort int
logPrefix string logPrefix string
log.Logger log.Logger
} }
func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager { func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
return &ProxyManager{ return &ProxyManager{
proxies: make(map[string]*ProxyWrapper), proxies: make(map[string]*ProxyWrapper),
sendCh: msgSendCh, sendCh: msgSendCh,
closed: false, closed: false,
logPrefix: logPrefix, clientCfg: clientCfg,
Logger: log.NewPrefixLogger(logPrefix), serverUDPPort: serverUDPPort,
logPrefix: logPrefix,
Logger: log.NewPrefixLogger(logPrefix),
} }
} }
@@ -58,12 +65,12 @@ func (pm *ProxyManager) Close() {
pm.proxies = make(map[string]*ProxyWrapper) pm.proxies = make(map[string]*ProxyWrapper)
} }
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) { func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
pm.mu.RLock() pm.mu.RLock()
pw, ok := pm.proxies[name] pw, ok := pm.proxies[name]
pm.mu.RUnlock() pm.mu.RUnlock()
if ok { if ok {
pw.InWorkConn(workConn) pw.InWorkConn(workConn, m)
} else { } else {
workConn.Close() workConn.Close()
} }
@@ -126,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
addPxyNames := make([]string, 0) addPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs { for name, cfg := range pxyCfgs {
if _, ok := pm.proxies[name]; !ok { if _, ok := pm.proxies[name]; !ok {
pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix) pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
pm.proxies[name] = pxy pm.proxies[name] = pxy
addPxyNames = append(addPxyNames, name) addPxyNames = append(addPxyNames, name)

View File

@@ -65,7 +65,7 @@ type ProxyWrapper struct {
log.Logger log.Logger
} }
func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper { func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
baseInfo := cfg.GetBaseInfo() baseInfo := cfg.GetBaseInfo()
pw := &ProxyWrapper{ pw := &ProxyWrapper{
ProxyStatus: ProxyStatus{ ProxyStatus: ProxyStatus{
@@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
pw.Trace("enable health check monitor") pw.Trace("enable health check monitor")
} }
pw.pxy = NewProxy(pw.Cfg) pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
return pw return pw
} }
@@ -217,13 +217,13 @@ func (pw *ProxyWrapper) statusFailedCallback() {
pw.Info("health check failed") pw.Info("health check failed")
} }
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) { func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
pw.mu.RLock() pw.mu.RLock()
pxy := pw.pxy pxy := pw.pxy
pw.mu.RUnlock() pw.mu.RUnlock()
if pxy != nil { if pxy != nil {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn) go pxy.InWorkConn(workConn, m)
} else { } else {
workConn.Close() workConn.Close()
} }

View File

@@ -15,6 +15,7 @@
package client package client
import ( import (
"crypto/tls"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"runtime" "runtime"
@@ -22,7 +23,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -33,6 +34,7 @@ import (
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
) )
// Service is a client service.
type Service struct { type Service struct {
// uniq id got from frps, attach it in loginMsg // uniq id got from frps, attach it in loginMsg
runId string runId string
@@ -41,16 +43,27 @@ type Service struct {
ctl *Control ctl *Control
ctlMu sync.RWMutex ctlMu sync.RWMutex
cfg config.ClientCommonConf
pxyCfgs map[string]config.ProxyConf pxyCfgs map[string]config.ProxyConf
visitorCfgs map[string]config.VisitorConf visitorCfgs map[string]config.VisitorConf
cfgMu sync.RWMutex 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 exit uint32 // 0 means not exit
closedCh chan int closedCh chan int
} }
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (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{ svr = &Service{
cfg: cfg,
cfgFile: cfgFile,
pxyCfgs: pxyCfgs, pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs, visitorCfgs: visitorCfgs,
exit: 0, exit: 0,
@@ -74,14 +87,14 @@ func (svr *Service) Run() error {
// if login_fail_exit is true, just exit this program // if login_fail_exit is true, just exit this program
// otherwise sleep a while and try again to connect to server // otherwise sleep a while and try again to connect to server
if g.GlbClientCfg.LoginFailExit { if svr.cfg.LoginFailExit {
return err return err
} else { } else {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
} }
} else { } else {
// login success // login success
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs) ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
ctl.Run() ctl.Run()
svr.ctlMu.Lock() svr.ctlMu.Lock()
svr.ctl = ctl svr.ctl = ctl
@@ -92,12 +105,18 @@ func (svr *Service) Run() error {
go svr.keepControllerWorking() go svr.keepControllerWorking()
if g.GlbClientCfg.AdminPort != 0 { if svr.cfg.AdminPort != 0 {
err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort) // 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 { if err != nil {
log.Warn("run admin server error: %v", err) log.Warn("run admin server error: %v", err)
} }
log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort) log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
} }
<-svr.closedCh <-svr.closedCh
@@ -129,7 +148,7 @@ func (svr *Service) keepControllerWorking() {
// reconnect success, init delayTime // reconnect success, init delayTime
delayTime = time.Second delayTime = time.Second
ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs) ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
ctl.Run() ctl.Run()
svr.ctlMu.Lock() svr.ctlMu.Lock()
svr.ctl = ctl svr.ctl = ctl
@@ -143,8 +162,14 @@ func (svr *Service) keepControllerWorking() {
// conn: control connection // conn: control connection
// session: if it's not nil, using tcp mux // session: if it's not nil, using tcp mux
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, var tlsConfig *tls.Config
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) 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 { if err != nil {
return return
} }
@@ -152,10 +177,13 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
defer func() { defer func() {
if err != nil { if err != nil {
conn.Close() conn.Close()
if session != nil {
session.Close()
}
} }
}() }()
if g.GlbClientCfg.TcpMux { if svr.cfg.TcpMux {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = ioutil.Discard
@@ -176,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
loginMsg := &msg.Login{ loginMsg := &msg.Login{
Arch: runtime.GOARCH, Arch: runtime.GOARCH,
Os: runtime.GOOS, Os: runtime.GOOS,
PoolCount: g.GlbClientCfg.PoolCount, PoolCount: svr.cfg.PoolCount,
User: g.GlbClientCfg.User, User: svr.cfg.User,
Version: version.Full(), Version: version.Full(),
PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now), PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now),
Timestamp: now, Timestamp: now,
RunId: svr.runId, RunId: svr.runId,
} }
@@ -202,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
} }
svr.runId = loginRespMsg.RunId svr.runId = loginRespMsg.RunId
g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort svr.serverUDPPort = loginRespMsg.ServerUdpPort
log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort) log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
return return
} }

View File

@@ -18,15 +18,11 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"golang.org/x/net/ipv4"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -35,6 +31,7 @@ import (
frpIo "github.com/fatedier/golib/io" frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
fmux "github.com/hashicorp/yamux"
) )
// Visitor is used for forward traffics from local port tot remote service. // Visitor is used for forward traffics from local port tot remote service.
@@ -52,12 +49,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
switch cfg := cfg.(type) { switch cfg := cfg.(type) {
case *config.StcpVisitorConf: case *config.StcpVisitorConf:
visitor = &StcpVisitor{ visitor = &StcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpVisitorConf: case *config.XtcpVisitorConf:
visitor = &XtcpVisitor{ visitor = &XtcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
} }
@@ -73,7 +70,7 @@ type BaseVisitor struct {
} }
type StcpVisitor struct { type StcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.StcpVisitorConf cfg *config.StcpVisitorConf
} }
@@ -160,7 +157,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
} }
type XtcpVisitor struct { type XtcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.XtcpVisitorConf cfg *config.XtcpVisitorConf
} }
@@ -195,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
defer userConn.Close() defer userConn.Close()
sv.Debug("get a new xtcp user connection") sv.Debug("get a new xtcp user connection")
if g.GlbClientCfg.ServerUdpPort == 0 { if sv.ctl.serverUDPPort == 0 {
sv.Error("xtcp is not supported by server") sv.Error("xtcp is not supported by server")
return return
} }
raddr, err := net.ResolveUDPAddr("udp", raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort)) fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
if err != nil { if err != nil {
sv.Error("resolve server UDP addr error") sv.Error("resolve server UDP addr error")
return return
@@ -249,40 +246,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
return return
} }
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) 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. // Close visitorConn, so we can use it's local address.
visitorConn.Close() visitorConn.Close()
// Send detect message. // send sid message to client
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
if len(array) <= 1 {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
return
}
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
/* daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
for i := 1000; i < 65000; i++ {
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
}
*/
port, err := strconv.ParseInt(array[1], 10, 64)
if err != nil { if err != nil {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) sv.Error("resolve client udp address error: %v", err)
return return
} }
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) lConn, err := net.DialUDP("udp", laddr, daddr)
sv.Trace("send all detect msg done") if err != nil {
sv.Error("dial client udp address error: %v", err)
return
}
defer lConn.Close()
// Listen for visitorConn's address and wait for client connection. lConn.Write([]byte(natHoleRespMsg.Sid))
lConn, err := net.ListenUDP("udp", laddr)
if err != nil { // read ack sid from client
sv.Error("listen on visitorConn's local adress error: %v", err)
return
}
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
sidBuf := pool.GetBuf(1024) sidBuf := pool.GetBuf(1024)
n, _, err = lConn.ReadFromUDP(sidBuf) lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
n, err = lConn.Read(sidBuf)
if err != nil { if err != nil {
sv.Warn("get sid from client error: %v", err) sv.Warn("get sid from client error: %v", err)
return return
@@ -292,11 +280,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
sv.Warn("incorrect sid from client") sv.Warn("incorrect sid from client")
return return
} }
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
pool.PutBuf(sidBuf) pool.PutBuf(sidBuf)
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
// wrap kcp connection
var remote io.ReadWriteCloser var remote io.ReadWriteCloser
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr) remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
if err != nil { if err != nil {
sv.Error("create kcp connection from udp connection error: %v", err) sv.Error("create kcp connection from udp connection error: %v", err)
return return
@@ -314,25 +304,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
remote = frpIo.WithCompression(remote) remote = frpIo.WithCompression(remote)
} }
frpIo.Join(userConn, 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") sv.Debug("join connections closed")
} }
func (sv *XtcpVisitor) 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
}

View File

@@ -12,9 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frpc" package main
import ( import (
"math/rand"
"time"
_ "github.com/fatedier/frp/assets/frpc/statik"
"github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
@@ -22,6 +26,7 @@ import (
func main() { func main() {
crypto.DefaultSalt = "frp" crypto.DefaultSalt = "frp"
rand.Seed(time.Now().UnixNano())
sub.Execute() sub.Execute()
} }

View File

@@ -33,6 +33,7 @@ func init() {
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
Use: "http", Use: "http",
Short: "Run frpc with a single http proxy", Short: "Run frpc with a single http proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
proxyConfs := map[string]config.ProxyConf{ proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg, cfg.ProxyName: cfg,
} }
err = startService(proxyConfs, nil) err = startService(clientCfg, proxyConfs, nil, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -33,6 +33,7 @@ func init() {
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
Use: "https", Use: "https",
Short: "Run frpc with a single https proxy", Short: "Run frpc with a single https proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
proxyConfs := map[string]config.ProxyConf{ proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg, cfg.ProxyName: cfg,
} }
err = startService(proxyConfs, nil) err = startService(clientCfg, proxyConfs, nil, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -16,7 +16,6 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -25,8 +24,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
) )
@@ -44,13 +41,13 @@ var reloadCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = parseClientCommonCfg(CfgFileTypeIni, iniContent) clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
err = reload() err = reload(clientCfg)
if err != nil { if err != nil {
fmt.Printf("frpc reload error: %v\n", err) fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -60,40 +57,34 @@ var reloadCmd = &cobra.Command{
}, },
} }
func reload() error { func reload(clientCfg config.ClientCommonConf) error {
if g.GlbClientCfg.AdminPort == 0 { if clientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use reload feature") return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
} }
req, err := http.NewRequest("GET", "http://"+ req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil) clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
if err != nil { if err != nil {
return err return err
} }
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+ authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
g.GlbClientCfg.AdminPwd)) clientCfg.AdminPwd))
req.Header.Add("Authorization", authStr) req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} else if res.Code != 0 {
return fmt.Errorf(res.Msg)
}
} }
return nil 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)))
} }

View File

@@ -28,7 +28,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/client" "github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
@@ -43,13 +42,14 @@ var (
cfgFile string cfgFile string
showVersion bool showVersion bool
serverAddr string serverAddr string
user string user string
protocol string protocol string
token string token string
logLevel string logLevel string
logFile string logFile string
logMaxDays int logMaxDays int
disableLogColor bool
proxyName string proxyName string
localIp string localIp string
@@ -73,7 +73,7 @@ var (
) )
func init() { func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc") rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
kcpDoneCh = make(chan struct{}) kcpDoneCh = make(chan struct{})
@@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) {
close(kcpDoneCh) close(kcpDoneCh)
} }
func parseClientCommonCfg(fileType int, content string) (err error) { func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
if fileType == CfgFileTypeIni { if fileType == CfgFileTypeIni {
err = parseClientCommonCfgFromIni(content) cfg, err = parseClientCommonCfgFromIni(content)
} else if fileType == CfgFileTypeCmd { } else if fileType == CfgFileTypeCmd {
err = parseClientCommonCfgFromCmd() cfg, err = parseClientCommonCfgFromCmd()
} }
if err != nil { if err != nil {
return return
} }
err = g.GlbClientCfg.ClientCommonConf.Check() err = cfg.Check()
if err != nil { if err != nil {
return return
} }
return return
} }
func parseClientCommonCfgFromIni(content string) (err error) { func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content) cfg, err := config.UnmarshalClientConfFromIni(content)
if err != nil { if err != nil {
return err return config.ClientCommonConf{}, err
} }
g.GlbClientCfg.ClientCommonConf = *cfg return cfg, err
return
} }
func parseClientCommonCfgFromCmd() (err error) { func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf()
strs := strings.Split(serverAddr, ":") strs := strings.Split(serverAddr, ":")
if len(strs) < 2 { if len(strs) < 2 {
err = fmt.Errorf("invalid server_addr") err = fmt.Errorf("invalid server_addr")
return return
} }
if strs[0] != "" { if strs[0] != "" {
g.GlbClientCfg.ServerAddr = strs[0] cfg.ServerAddr = strs[0]
} }
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1]) cfg.ServerPort, err = strconv.Atoi(strs[1])
if err != nil { if err != nil {
err = fmt.Errorf("invalid server_addr") err = fmt.Errorf("invalid server_addr")
return return
} }
g.GlbClientCfg.User = user cfg.User = user
g.GlbClientCfg.Protocol = protocol cfg.Protocol = protocol
g.GlbClientCfg.Token = token cfg.Token = token
g.GlbClientCfg.LogLevel = logLevel cfg.LogLevel = logLevel
g.GlbClientCfg.LogFile = logFile cfg.LogFile = logFile
g.GlbClientCfg.LogMaxDays = int64(logMaxDays) cfg.LogMaxDays = int64(logMaxDays)
if logFile == "console" { if logFile == "console" {
g.GlbClientCfg.LogWay = "console" cfg.LogWay = "console"
} else { } else {
g.GlbClientCfg.LogWay = "file" cfg.LogWay = "file"
} }
return nil cfg.DisableLogColor = disableLogColor
return
} }
func runClient(cfgFilePath string) (err error) { func runClient(cfgFilePath string) (err error) {
@@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) {
if err != nil { if err != nil {
return return
} }
g.GlbClientCfg.CfgFile = cfgFilePath
err = parseClientCommonCfg(CfgFileTypeIni, content) cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
if err != nil { if err != nil {
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
if err != nil { if err != nil {
return err return err
} }
err = startService(pxyCfgs, visitorCfgs) err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
return return
} }
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) { func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays) log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
if g.GlbClientCfg.DnsServer != "" { cfg.LogMaxDays, cfg.DisableLogColor)
s := g.GlbClientCfg.DnsServer
if cfg.DnsServer != "" {
s := cfg.DnsServer
if !strings.Contains(s, ":") { if !strings.Contains(s, ":") {
s += ":53" s += ":53"
} }
@@ -205,15 +209,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
}, },
} }
} }
svr := client.NewService(pxyCfgs, visitorCfgs) svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
}
// Capture the exit signal if we use kcp. // Capture the exit signal if we use kcp.
if g.GlbClientCfg.Protocol == "kcp" { if cfg.Protocol == "kcp" {
go handleSignal(svr) go handleSignal(svr)
} }
err = svr.Run() err = svr.Run()
if g.GlbClientCfg.Protocol == "kcp" { if cfg.Protocol == "kcp" {
<-kcpDoneCh <-kcpDoneCh
} }
return return

View File

@@ -27,7 +27,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/client" "github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
) )
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = parseClientCommonCfg(CfgFileTypeIni, iniContent) clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
err = status() err = status(clientCfg)
if err != nil { if err != nil {
fmt.Printf("frpc get status error: %v\n", err) fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -60,94 +59,96 @@ var statusCmd = &cobra.Command{
}, },
} }
func status() error { func status(clientCfg config.ClientCommonConf) error {
if g.GlbClientCfg.AdminPort == 0 { if clientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to get proxy status") return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
} }
req, err := http.NewRequest("GET", "http://"+ req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil) clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
if err != nil { if err != nil {
return err return err
} }
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+ authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
g.GlbClientCfg.AdminPwd)) clientCfg.AdminPwd))
req.Header.Add("Authorization", authStr) req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
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("")
}
} }
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 return nil
} }

View File

@@ -32,6 +32,7 @@ func init() {
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
Use: "stcp", Use: "stcp",
Short: "Run frpc with a single stcp proxy", Short: "Run frpc with a single stcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = startService(proxyConfs, visitorConfs) err = startService(clientCfg, proxyConfs, visitorConfs, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -32,6 +32,7 @@ func init() {
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
Use: "tcp", Use: "tcp",
Short: "Run frpc with a single tcp proxy", Short: "Run frpc with a single tcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
proxyConfs := map[string]config.ProxyConf{ proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg, cfg.ProxyName: cfg,
} }
err = startService(proxyConfs, nil) err = startService(clientCfg, proxyConfs, nil, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -32,6 +32,7 @@ func init() {
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
Use: "udp", Use: "udp",
Short: "Run frpc with a single udp proxy", Short: "Run frpc with a single udp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
proxyConfs := map[string]config.ProxyConf{ proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg, cfg.ProxyName: cfg,
} }
err = startService(proxyConfs, nil) err = startService(clientCfg, proxyConfs, nil, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -32,6 +32,7 @@ func init() {
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") 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().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(&proxyName, "proxy_name", "n", "", "proxy name")
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
Use: "xtcp", Use: "xtcp",
Short: "Run frpc with a single xtcp proxy", Short: "Run frpc with a single xtcp proxy",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "") clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -68,7 +69,7 @@ var xtcpCmd = &cobra.Command{
if role == "server" { if role == "server" {
cfg := &config.XtcpProxyConf{} cfg := &config.XtcpProxyConf{}
cfg.ProxyName = prefix + proxyName cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy cfg.ProxyType = consts.XtcpProxy
cfg.UseEncryption = useEncryption cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression cfg.UseCompression = useCompression
cfg.Role = role cfg.Role = role
@@ -84,7 +85,7 @@ var xtcpCmd = &cobra.Command{
} else if role == "visitor" { } else if role == "visitor" {
cfg := &config.XtcpVisitorConf{} cfg := &config.XtcpVisitorConf{}
cfg.ProxyName = prefix + proxyName cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy cfg.ProxyType = consts.XtcpProxy
cfg.UseEncryption = useEncryption cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression cfg.UseCompression = useCompression
cfg.Role = role cfg.Role = role
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = startService(proxyConfs, visitorConfs) err = startService(clientCfg, proxyConfs, visitorConfs, "")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -12,14 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frps" package main
import ( import (
"math/rand"
"time"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps/statik"
) )
func main() { func main() {
crypto.DefaultSalt = "frp" crypto.DefaultSalt = "frp"
rand.Seed(time.Now().UnixNano())
Execute() Execute()
} }

View File

@@ -20,7 +20,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server" "github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -53,6 +52,7 @@ var (
logFile string logFile string
logLevel string logLevel string
logMaxDays int64 logMaxDays int64
disableLogColor bool
token string token string
subDomainHost string subDomainHost string
tcpMux bool tcpMux bool
@@ -62,7 +62,7 @@ var (
) )
func init() { func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps") rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address") rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
@@ -79,7 +79,9 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password") rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file") rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days") 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(&token, "token", "t", "", "auth token")
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host") rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports") rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
@@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{
return nil return nil
} }
var cfg config.ServerCommonConf
var err error var err error
if cfgFile != "" { if cfgFile != "" {
var content string var content string
@@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{
if err != nil { if err != nil {
return err return err
} }
g.GlbServerCfg.CfgFile = cfgFile cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
err = parseServerCommonCfg(CfgFileTypeIni, content)
} else { } else {
err = parseServerCommonCfg(CfgFileTypeCmd, "") cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
} }
if err != nil { if err != nil {
return err return err
} }
err = runServer() err = runServer(cfg)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@@ -126,52 +128,51 @@ func Execute() {
} }
} }
func parseServerCommonCfg(fileType int, content string) (err error) { func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
if fileType == CfgFileTypeIni { if fileType == CfgFileTypeIni {
err = parseServerCommonCfgFromIni(content) cfg, err = parseServerCommonCfgFromIni(content)
} else if fileType == CfgFileTypeCmd { } else if fileType == CfgFileTypeCmd {
err = parseServerCommonCfgFromCmd() cfg, err = parseServerCommonCfgFromCmd()
} }
if err != nil { if err != nil {
return return
} }
err = g.GlbServerCfg.ServerCommonConf.Check() err = cfg.Check()
if err != nil { if err != nil {
return return
} }
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
return return
} }
func parseServerCommonCfgFromIni(content string) (err error) { func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content) cfg, err := config.UnmarshalServerConfFromIni(content)
if err != nil { if err != nil {
return err return config.ServerCommonConf{}, err
} }
g.GlbServerCfg.ServerCommonConf = *cfg return cfg, nil
return
} }
func parseServerCommonCfgFromCmd() (err error) { func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
g.GlbServerCfg.BindAddr = bindAddr cfg = config.GetDefaultServerConf()
g.GlbServerCfg.BindPort = bindPort
g.GlbServerCfg.BindUdpPort = bindUdpPort cfg.BindAddr = bindAddr
g.GlbServerCfg.KcpBindPort = kcpBindPort cfg.BindPort = bindPort
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr cfg.BindUdpPort = bindUdpPort
g.GlbServerCfg.VhostHttpPort = vhostHttpPort cfg.KcpBindPort = kcpBindPort
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort cfg.ProxyBindAddr = proxyBindAddr
g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout cfg.VhostHttpPort = vhostHttpPort
g.GlbServerCfg.DashboardAddr = dashboardAddr cfg.VhostHttpsPort = vhostHttpsPort
g.GlbServerCfg.DashboardPort = dashboardPort cfg.VhostHttpTimeout = vhostHttpTimeout
g.GlbServerCfg.DashboardUser = dashboardUser cfg.DashboardAddr = dashboardAddr
g.GlbServerCfg.DashboardPwd = dashboardPwd cfg.DashboardPort = dashboardPort
g.GlbServerCfg.LogFile = logFile cfg.DashboardUser = dashboardUser
g.GlbServerCfg.LogLevel = logLevel cfg.DashboardPwd = dashboardPwd
g.GlbServerCfg.LogMaxDays = logMaxDays cfg.LogFile = logFile
g.GlbServerCfg.Token = token cfg.LogLevel = logLevel
g.GlbServerCfg.SubDomainHost = subDomainHost cfg.LogMaxDays = logMaxDays
cfg.Token = token
cfg.SubDomainHost = subDomainHost
if len(allowPorts) > 0 { if len(allowPorts) > 0 {
// e.g. 1000-2000,2001,2002,3000-4000 // e.g. 1000-2000,2001,2002,3000-4000
ports, errRet := util.ParseRangeNumbers(allowPorts) ports, errRet := util.ParseRangeNumbers(allowPorts)
@@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) {
} }
for _, port := range ports { for _, port := range ports {
g.GlbServerCfg.AllowPorts[int(port)] = struct{}{} cfg.AllowPorts[int(port)] = struct{}{}
} }
} }
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient cfg.MaxPortsPerClient = maxPortsPerClient
if logFile == "console" { if logFile == "console" {
g.GlbClientCfg.LogWay = "console" cfg.LogWay = "console"
} else { } else {
g.GlbClientCfg.LogWay = "file" cfg.LogWay = "file"
} }
cfg.DisableLogColor = disableLogColor
return return
} }
func runServer() (err error) { func runServer(cfg config.ServerCommonConf) (err error) {
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel, log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
g.GlbServerCfg.LogMaxDays) svr, err := server.NewService(cfg)
svr, err := server.NewService()
if err != nil { if err != nil {
return err return err
} }
log.Info("Start frps success") log.Info("Start frps success")
server.ServerService = svr
svr.Run() svr.Run()
return return
} }

View File

@@ -18,6 +18,9 @@ log_level = info
log_max_days = 3 log_max_days = 3
# disable log colors when log_file is console, default is false
disable_log_color = false
# for authentication # for authentication
token = 12345678 token = 12345678
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
admin_port = 7400 admin_port = 7400
admin_user = admin admin_user = admin
admin_pwd = 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 # connections will be established in advance, default value is zero
pool_count = 5 pool_count = 5
@@ -44,10 +49,13 @@ login_fail_exit = true
# now it supports tcp and kcp and websocket, default is tcp # now it supports tcp and kcp and websocket, default is tcp
protocol = tcp protocol = tcp
# if tls_enable is true, frpc will connect frps by tls
tls_enable = true
# specify a dns server, so frpc will use this instead of default one # specify a dns server, so frpc will use this instead of default one
# dns_server = 8.8.8.8 # dns_server = 8.8.8.8
# proxy names you want to start divided by ',' # proxy names you want to start seperated by ','
# default is empty, means all proxies # default is empty, means all proxies
# start = ssh,dns # start = ssh,dns
@@ -151,6 +159,9 @@ use_encryption = false
use_compression = false use_compression = false
subdomain = web01 subdomain = web01
custom_domains = web02.yourdomain.com 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] [plugin_unix_domain_socket]
type = tcp type = tcp
@@ -184,6 +195,16 @@ plugin_strip_prefix = static
plugin_http_user = abc plugin_http_user = abc
plugin_http_passwd = 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] [secret_tcp]
# If the type is secret tcp, remote_port is useless # If the type is secret tcp, remote_port is useless
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor # Who want to connect local port should deploy another frpc with stcp proxy and role is visitor

View File

@@ -43,6 +43,9 @@ log_level = info
log_max_days = 3 log_max_days = 3
# disable log colors when log_file is console, default is false
disable_log_color = false
# auth token # auth token
token = 12345678 token = 12345678
@@ -65,3 +68,6 @@ subdomain_host = frps.com
# if tcp stream multiplexing is used, default is true # if tcp stream multiplexing is used, default is true
tcp_mux = 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

32
g/g.go
View File

@@ -1,32 +0,0 @@
package g
import (
"github.com/fatedier/frp/models/config"
)
var (
GlbClientCfg *ClientCfg
GlbServerCfg *ServerCfg
)
func init() {
GlbClientCfg = &ClientCfg{
ClientCommonConf: *config.GetDefaultClientConf(),
}
GlbServerCfg = &ServerCfg{
ServerCommonConf: *config.GetDefaultServerConf(),
}
}
type ClientCfg struct {
config.ClientCommonConf
CfgFile string
ServerUdpPort int // this is configured by login response from frps
}
type ServerCfg struct {
config.ServerCommonConf
CfgFile string
}

22
go.mod
View File

@@ -4,29 +4,29 @@ go 1.12
require ( require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4 github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.7.3
github.com/gorilla/mux v1.6.2 github.com/gorilla/websocket v1.4.0
github.com/gorilla/websocket v1.2.0 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0
github.com/inconshreveable/mousetrap v1.0.0 // indirect 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/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/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rakyll/statik v0.1.1 github.com/rakyll/statik v0.1.1
github.com/rodaine/table v1.0.0 github.com/rodaine/table v1.0.0
github.com/spf13/cobra v0.0.3 github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.1 // indirect github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/testify v1.2.1 github.com/stretchr/testify v1.3.0
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76 // indirect
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/text v0.3.2 // indirect
) )

53
go.sum
View File

@@ -1,30 +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/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/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/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 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM= github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= 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/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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/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/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/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/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/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/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76/go.mod h1:ToWcj2sZ6xHl14JjZiVDktYpFtrFZJXBlsu7TV23lNg= 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/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/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/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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,33 +23,103 @@ import (
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
) )
// 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 { type ClientCommonConf struct {
ServerAddr string `json:"server_addr"` // ServerAddr specifies the address of the server to connect to. By
ServerPort int `json:"server_port"` // default, this value is "0.0.0.0".
HttpProxy string `json:"http_proxy"` ServerAddr string `json:"server_addr"`
LogFile string `json:"log_file"` // ServerPort specifies the port to connect to the server on. By default,
LogWay string `json:"log_way"` // this value is 7000.
LogLevel string `json:"log_level"` ServerPort int `json:"server_port"`
LogMaxDays int64 `json:"log_max_days"` // HttpProxy specifies a proxy address to connect to the server through. If
Token string `json:"token"` // this value is "", the server will be connected to directly. By default,
AdminAddr string `json:"admin_addr"` // this value is read from the "http_proxy" environment variable.
AdminPort int `json:"admin_port"` HttpProxy string `json:"http_proxy"`
AdminUser string `json:"admin_user"` // LogFile specifies a file where logs will be written to. This value will
AdminPwd string `json:"admin_pwd"` // only be used if LogWay is set appropriately. By default, this value is
PoolCount int `json:"pool_count"` // "console".
TcpMux bool `json:"tcp_mux"` LogFile string `json:"log_file"`
User string `json:"user"` // LogWay specifies the way logging is managed. Valid values are "console"
DnsServer string `json:"dns_server"` // or "file". If "console" is used, logs will be printed to stdout. If
LoginFailExit bool `json:"login_fail_exit"` // "file" is used, logs will be printed to LogFile. By default, this value
Start map[string]struct{} `json:"start"` // is "console".
Protocol string `json:"protocol"` LogWay string `json:"log_way"`
HeartBeatInterval int64 `json:"heartbeat_interval"` // LogLevel specifies the minimum log level. Valid values are "trace",
HeartBeatTimeout int64 `json:"heartbeat_timeout"` // "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 GetDefaultClientConf() *ClientCommonConf { // GetDefaultClientConf returns a client configuration with default values.
return &ClientCommonConf{ func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
HttpProxy: os.Getenv("http_proxy"), HttpProxy: os.Getenv("http_proxy"),
@@ -57,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf {
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
DisableLogColor: false,
Token: "", Token: "",
AdminAddr: "127.0.0.1", AdminAddr: "127.0.0.1",
AdminPort: 0, AdminPort: 0,
AdminUser: "", AdminUser: "",
AdminPwd: "", AdminPwd: "",
AssetsDir: "",
PoolCount: 1, PoolCount: 1,
TcpMux: true, TcpMux: true,
User: "", User: "",
@@ -69,21 +141,18 @@ func GetDefaultClientConf() *ClientCommonConf {
LoginFailExit: true, LoginFailExit: true,
Start: make(map[string]struct{}), Start: make(map[string]struct{}),
Protocol: "tcp", Protocol: "tcp",
TLSEnable: false,
HeartBeatInterval: 30, HeartBeatInterval: 30,
HeartBeatTimeout: 90, HeartBeatTimeout: 90,
} }
} }
func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) { func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
cfg = defaultCfg cfg = GetDefaultClientConf()
if cfg == nil {
cfg = GetDefaultClientConf()
}
conf, err := ini.Load(strings.NewReader(content)) conf, err := ini.Load(strings.NewReader(content))
if err != nil { if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err) return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
return nil, err
} }
var ( var (
@@ -104,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
cfg.ServerPort = int(v) cfg.ServerPort = int(v)
} }
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
cfg.DisableLogColor = true
}
if tmpStr, ok = conf.Get("common", "http_proxy"); ok { if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
cfg.HttpProxy = tmpStr cfg.HttpProxy = tmpStr
} }
@@ -152,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
cfg.AdminPwd = tmpStr cfg.AdminPwd = tmpStr
} }
if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
cfg.AssetsDir = tmpStr
}
if tmpStr, ok = conf.Get("common", "pool_count"); ok { if tmpStr, ok = conf.Get("common", "pool_count"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.PoolCount = int(v) cfg.PoolCount = int(v)
@@ -194,6 +271,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
cfg.Protocol = tmpStr cfg.Protocol = tmpStr
} }
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
cfg.TLSEnable = true
} else {
cfg.TLSEnable = false
}
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")

View File

@@ -58,11 +58,11 @@ type ProxyConf interface {
UnmarshalFromIni(prefix string, name string, conf ini.Section) error UnmarshalFromIni(prefix string, name string, conf ini.Section) error
MarshalToMsg(pMsg *msg.NewProxy) MarshalToMsg(pMsg *msg.NewProxy)
CheckForCli() error CheckForCli() error
CheckForSvr() error CheckForSvr(serverCfg ServerCommonConf) error
Compare(conf ProxyConf) bool Compare(conf ProxyConf) bool
} }
func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) { func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" { if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy pMsg.ProxyType = consts.TcpProxy
} }
@@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
return return
} }
cfg.UnmarshalFromMsg(pMsg) cfg.UnmarshalFromMsg(pMsg)
err = cfg.CheckForSvr() err = cfg.CheckForSvr(serverCfg)
return return
} }
@@ -97,18 +97,36 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
return return
} }
// BaseProxy info // BaseProxyConf provides configuration info that is common to all proxy types.
type BaseProxyConf struct { type BaseProxyConf struct {
// ProxyName is the name of this proxy.
ProxyName string `json:"proxy_name"` ProxyName string `json:"proxy_name"`
// ProxyType specifies the type of this proxy. Valid values include "tcp",
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
// "tcp".
ProxyType string `json:"proxy_type"` ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"` // UseEncryption controls whether or not communication with the server will
UseCompression bool `json:"use_compression"` // be encrypted. Encryption is done using the tokens supplied in the server
Group string `json:"group"` // and client configuration. By default, this value is false.
GroupKey string `json:"group_key"` UseEncryption bool `json:"use_encryption"`
// UseCompression controls whether or not communication with the server
// will be compressed. By default, this value is false.
UseCompression bool `json:"use_compression"`
// Group specifies which group the proxy is a part of. The server will use
// this information to load balance proxies in the same group. If the value
// is "", this proxy will not be in a group. By default, this value is "".
Group string `json:"group"`
// GroupKey specifies a group key, which should be the same among proxies
// of the same group. By default, this value is "".
GroupKey string `json:"group_key"`
// ProxyProtocolVersion specifies which protocol version to use. Valid
// values include "v1", "v2", and "". If the value is "", a protocol
// version will be automatically selected. By default, this value is "".
ProxyProtocolVersion string `json:"proxy_protocol_version"`
LocalSvrConf LocalSvrConf
HealthCheckConf // only used for client HealthCheckConf
} }
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
@@ -121,7 +139,8 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
cfg.UseEncryption != cmp.UseEncryption || cfg.UseEncryption != cmp.UseEncryption ||
cfg.UseCompression != cmp.UseCompression || cfg.UseCompression != cmp.UseCompression ||
cfg.Group != cmp.Group || cfg.Group != cmp.Group ||
cfg.GroupKey != cmp.GroupKey { cfg.GroupKey != cmp.GroupKey ||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
return false return false
} }
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
@@ -162,6 +181,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
cfg.Group = section["group"] cfg.Group = section["group"]
cfg.GroupKey = section["group_key"] cfg.GroupKey = section["group_key"]
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return err return err
@@ -194,6 +214,12 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
} }
func (cfg *BaseProxyConf) checkForCli() (err error) { func (cfg *BaseProxyConf) checkForCli() (err error) {
if cfg.ProxyProtocolVersion != "" {
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
}
}
if err = cfg.LocalSvrConf.checkForCli(); err != nil { if err = cfg.LocalSvrConf.checkForCli(); err != nil {
return return
} }
@@ -298,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
return return
} }
func (cfg *DomainConf) checkForSvr() (err error) { func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
if err = cfg.check(); err != nil { if err = cfg.check(); err != nil {
return return
} }
for _, domain := range cfg.CustomDomains { for _, domain := range cfg.CustomDomains {
if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) { if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, subDomainHost) { if strings.Contains(domain, serverCfg.SubDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost) return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
} }
} }
} }
if cfg.SubDomain != "" { if cfg.SubDomain != "" {
if subDomainHost == "" { if serverCfg.SubDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps") return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
} }
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") { if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
@@ -322,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
return return
} }
// Local service info // LocalSvrConf configures what location the client will proxy to, or what
// plugin will be used.
type LocalSvrConf struct { type LocalSvrConf struct {
LocalIp string `json:"local_ip"` // LocalIp specifies the IP address or host name to proxy to.
LocalPort int `json:"local_port"` LocalIp string `json:"local_ip"`
// LocalPort specifies the port to proxy to.
LocalPort int `json:"local_port"`
Plugin string `json:"plugin"` // Plugin specifies what plugin should be used for proxying. If this value
// is set, the LocalIp and LocalPort values will be ignored. By default,
// this value is "".
Plugin string `json:"plugin"`
// PluginParams specify parameters to be passed to the plugin, if one is
// being used. By default, this value is an empty map.
PluginParams map[string]string `json:"plugin_params"` PluginParams map[string]string `json:"plugin_params"`
} }
@@ -389,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
return return
} }
// Health check info // HealthCheckConf configures health checking. This can be useful for load
// balancing purposes to detect and remove proxies to failing services.
type HealthCheckConf struct { type HealthCheckConf struct {
HealthCheckType string `json:"health_check_type"` // tcp | http // HealthCheckType specifies what protocol to use for health checking.
HealthCheckTimeoutS int `json:"health_check_timeout_s"` // Valid values include "tcp", "http", and "". If this value is "", health
HealthCheckMaxFailed int `json:"health_check_max_failed"` // checking will not be performed. By default, this value is "".
HealthCheckIntervalS int `json:"health_check_interval_s"` //
HealthCheckUrl string `json:"health_check_url"` // If the type is "tcp", a connection will be attempted to the target
// server. If a connection cannot be established, the health check fails.
// local_ip + local_port //
// If the type is "http", a GET request will be made to the endpoint
// specified by HealthCheckUrl. If the response is not a 200, the health
// check fails.
HealthCheckType string `json:"health_check_type"` // tcp | http
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
// check attempt to connect. If the timeout is reached, this counts as a
// health check failure. By default, this value is 3.
HealthCheckTimeoutS int `json:"health_check_timeout_s"`
// HealthCheckMaxFailed specifies the number of allowed failures before the
// proxy is stopped. By default, this value is 1.
HealthCheckMaxFailed int `json:"health_check_max_failed"`
// HealthCheckIntervalS specifies the time in seconds between health
// checks. By default, this value is 10.
HealthCheckIntervalS int `json:"health_check_interval_s"`
// HealthCheckUrl specifies the address to send health checks to if the
// health check type is "http".
HealthCheckUrl string `json:"health_check_url"`
// HealthCheckAddr specifies the address to connect to if the health check
// type is "tcp".
HealthCheckAddr string `json:"-"` HealthCheckAddr string `json:"-"`
} }
@@ -494,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *TcpProxyConf) CheckForSvr() error { return nil } func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
// UDP // UDP
type UdpProxyConf struct { type UdpProxyConf struct {
@@ -542,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *UdpProxyConf) CheckForSvr() error { return nil } func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
// HTTP // HTTP
type HttpProxyConf struct { type HttpProxyConf struct {
@@ -647,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *HttpProxyConf) CheckForSvr() (err error) { func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if vhostHttpPort == 0 { if serverCfg.VhostHttpPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set") return fmt.Errorf("type [http] not support when vhost_http_port is not set")
} }
if err = cfg.DomainConf.checkForSvr(); err != nil { if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return return
} }
@@ -707,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *HttpsProxyConf) CheckForSvr() (err error) { func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if vhostHttpsPort == 0 { if serverCfg.VhostHttpsPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set") return fmt.Errorf("type [https] not support when vhost_https_port is not set")
} }
if err = cfg.DomainConf.checkForSvr(); err != nil { if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return return
} }
@@ -780,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *StcpProxyConf) CheckForSvr() (err error) { func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return return
} }
@@ -847,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
return return
} }
func (cfg *XtcpProxyConf) CheckForSvr() (err error) { func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return return
} }

View File

@@ -24,61 +24,122 @@ import (
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
) )
var ( // ServerCommonConf contains information for a server service. It is
// server global configure used for generate proxy conf used in frps // recommended to use GetDefaultServerConf instead of creating this object
proxyBindAddr string // directly, so that all unspecified fields have reasonable default values.
subDomainHost string
vhostHttpPort int
vhostHttpsPort int
)
func InitServerCfg(cfg *ServerCommonConf) {
proxyBindAddr = cfg.ProxyBindAddr
subDomainHost = cfg.SubDomainHost
vhostHttpPort = cfg.VhostHttpPort
vhostHttpsPort = cfg.VhostHttpsPort
}
// common config
type ServerCommonConf struct { type ServerCommonConf struct {
BindAddr string `json:"bind_addr"` // BindAddr specifies the address that the server binds to. By default,
BindPort int `json:"bind_port"` // this value is "0.0.0.0".
BindUdpPort int `json:"bind_udp_port"` BindAddr string `json:"bind_addr"`
KcpBindPort int `json:"kcp_bind_port"` // 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"` ProxyBindAddr string `json:"proxy_bind_addr"`
// If VhostHttpPort equals 0, don't listen a public port for http protocol. // 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"` VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol // VhostHttpsPort specifies the port that the server listens for HTTPS
VhostHttpsPort int `json:"vhost_http_port"` // 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"`
// 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"` VhostHttpTimeout int64 `json:"vhost_http_timeout"`
// DashboardAddr specifies the address that the dashboard binds to. By
// default, this value is "0.0.0.0".
DashboardAddr string `json:"dashboard_addr"` DashboardAddr string `json:"dashboard_addr"`
// if DashboardPort equals 0, dashboard is not available // DashboardPort specifies the port that the dashboard listens on. If this
DashboardPort int `json:"dashboard_port"` // 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 string `json:"dashboard_user"`
DashboardPwd string `json:"dashboard_pwd"` // DashboardUser specifies the password that the dashboard will use for
AssetsDir string `json:"asserts_dir"` // login. By default, this value is "admin".
LogFile string `json:"log_file"` DashboardPwd string `json:"dashboard_pwd"`
LogWay string `json:"log_way"` // console or file // AssetsDir specifies the local directory that the dashboard will load
LogLevel string `json:"log_level"` // resources from. If this value is "", assets will be loaded from the
LogMaxDays int64 `json:"log_max_days"` // bundled executable using statik. By default, this value is "".
Token string `json:"token"` 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"` SubDomainHost string `json:"subdomain_host"`
TcpMux bool `json:"tcp_mux"` // 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 map[int]struct{} // AllowPorts specifies a set of ports that clients are able to proxy to.
MaxPoolCount int64 `json:"max_pool_count"` // 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"` MaxPortsPerClient int64 `json:"max_ports_per_client"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"` // HeartBeatTimeout specifies the maximum time to wait for a heartbeat
UserConnTimeout int64 `json:"user_conn_timeout"` // 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 GetDefaultServerConf() *ServerCommonConf { // GetDefaultServerConf returns a server configuration with reasonable
return &ServerCommonConf{ // defaults.
func GetDefaultServerConf() ServerCommonConf {
return ServerCommonConf{
BindAddr: "0.0.0.0", BindAddr: "0.0.0.0",
BindPort: 7000, BindPort: 7000,
BindUdpPort: 0, BindUdpPort: 0,
@@ -96,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf {
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
DisableLogColor: false,
Token: "", Token: "",
SubDomainHost: "", SubDomainHost: "",
TcpMux: true, TcpMux: true,
@@ -104,19 +166,19 @@ func GetDefaultServerConf() *ServerCommonConf {
MaxPortsPerClient: 0, MaxPortsPerClient: 0,
HeartBeatTimeout: 90, HeartBeatTimeout: 90,
UserConnTimeout: 10, UserConnTimeout: 10,
Custom404Page: "",
} }
} }
func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) { // UnmarshalServerConfFromIni parses the contents of a server configuration ini
cfg = defaultCfg // file and returns the resulting server configuration.
if cfg == nil { func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
cfg = GetDefaultServerConf() cfg = GetDefaultServerConf()
}
conf, err := ini.Load(strings.NewReader(content)) conf, err := ini.Load(strings.NewReader(content))
if err != nil { if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err) err = fmt.Errorf("parse ini conf file error: %v", err)
return nil, err return ServerCommonConf{}, err
} }
var ( var (
@@ -242,6 +304,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
} }
} }
if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
cfg.DisableLogColor = true
}
cfg.Token, _ = conf.Get("common", "token") cfg.Token, _ = conf.Get("common", "token")
if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok { if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
@@ -293,6 +359,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
cfg.TcpMux = true cfg.TcpMux = true
} }
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
cfg.Custom404Page = tmpStr
}
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64) v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil { if errRet != nil {

View File

@@ -17,44 +17,46 @@ package msg
import "net" import "net"
const ( const (
TypeLogin = 'o' TypeLogin = 'o'
TypeLoginResp = '1' TypeLoginResp = '1'
TypeNewProxy = 'p' TypeNewProxy = 'p'
TypeNewProxyResp = '2' TypeNewProxyResp = '2'
TypeCloseProxy = 'c' TypeCloseProxy = 'c'
TypeNewWorkConn = 'w' TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r' TypeReqWorkConn = 'r'
TypeStartWorkConn = 's' TypeStartWorkConn = 's'
TypeNewVisitorConn = 'v' TypeNewVisitorConn = 'v'
TypeNewVisitorConnResp = '3' TypeNewVisitorConnResp = '3'
TypePing = 'h' TypePing = 'h'
TypePong = '4' TypePong = '4'
TypeUdpPacket = 'u' TypeUdpPacket = 'u'
TypeNatHoleVisitor = 'i' TypeNatHoleVisitor = 'i'
TypeNatHoleClient = 'n' TypeNatHoleClient = 'n'
TypeNatHoleResp = 'm' TypeNatHoleResp = 'm'
TypeNatHoleSid = '5' TypeNatHoleClientDetectOK = 'd'
TypeNatHoleSid = '5'
) )
var ( var (
msgTypeMap = map[byte]interface{}{ msgTypeMap = map[byte]interface{}{
TypeLogin: Login{}, TypeLogin: Login{},
TypeLoginResp: LoginResp{}, TypeLoginResp: LoginResp{},
TypeNewProxy: NewProxy{}, TypeNewProxy: NewProxy{},
TypeNewProxyResp: NewProxyResp{}, TypeNewProxyResp: NewProxyResp{},
TypeCloseProxy: CloseProxy{}, TypeCloseProxy: CloseProxy{},
TypeNewWorkConn: NewWorkConn{}, TypeNewWorkConn: NewWorkConn{},
TypeReqWorkConn: ReqWorkConn{}, TypeReqWorkConn: ReqWorkConn{},
TypeStartWorkConn: StartWorkConn{}, TypeStartWorkConn: StartWorkConn{},
TypeNewVisitorConn: NewVisitorConn{}, TypeNewVisitorConn: NewVisitorConn{},
TypeNewVisitorConnResp: NewVisitorConnResp{}, TypeNewVisitorConnResp: NewVisitorConnResp{},
TypePing: Ping{}, TypePing: Ping{},
TypePong: Pong{}, TypePong: Pong{},
TypeUdpPacket: UdpPacket{}, TypeUdpPacket: UdpPacket{},
TypeNatHoleVisitor: NatHoleVisitor{}, TypeNatHoleVisitor: NatHoleVisitor{},
TypeNatHoleClient: NatHoleClient{}, TypeNatHoleClient: NatHoleClient{},
TypeNatHoleResp: NatHoleResp{}, TypeNatHoleResp: NatHoleResp{},
TypeNatHoleSid: NatHoleSid{}, TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
TypeNatHoleSid: NatHoleSid{},
} }
) )
@@ -124,6 +126,10 @@ type ReqWorkConn struct {
type StartWorkConn struct { type StartWorkConn struct {
ProxyName string `json:"proxy_name"` ProxyName string `json:"proxy_name"`
SrcAddr string `json:"src_addr"`
DstAddr string `json:"dst_addr"`
SrcPort uint16 `json:"src_port"`
DstPort uint16 `json:"dst_port"`
} }
type NewVisitorConn struct { type NewVisitorConn struct {
@@ -169,6 +175,9 @@ type NatHoleResp struct {
Error string `json:"error"` Error string `json:"error"`
} }
type NatHoleClientDetectOK struct {
}
type NatHoleSid struct { type NatHoleSid struct {
Sid string `json:"sid"` Sid string `json:"sid"`
} }

View File

@@ -18,6 +18,11 @@ import (
// Timeout seconds. // Timeout seconds.
var NatHoleTimeout int64 = 10 var NatHoleTimeout int64 = 10
type SidRequest struct {
Sid string
NotifyCh chan struct{}
}
type NatHoleController struct { type NatHoleController struct {
listener *net.UDPConn listener *net.UDPConn
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
return nc, nil return nc, nil
} }
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) { func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
clientCfg := &NatHoleClientCfg{ clientCfg := &NatHoleClientCfg{
Name: name, Name: name,
Sk: sk, Sk: sk,
SidCh: make(chan string), SidCh: make(chan *SidRequest),
} }
nc.mu.Lock() nc.mu.Lock()
nc.clientCfgs[name] = clientCfg nc.clientCfgs[name] = clientCfg
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
}() }()
err := errors.PanicToError(func() { err := errors.PanicToError(func() {
clientCfg.SidCh <- sid clientCfg.SidCh <- &SidRequest{
Sid: sid,
NotifyCh: session.NotifyCh,
}
}) })
if err != nil { if err != nil {
return return
@@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
} }
log.Trace("handle client message, sid [%s]", session.Sid) log.Trace("handle client message, sid [%s]", session.Sid)
session.ClientAddr = raddr session.ClientAddr = raddr
session.NotifyCh <- struct{}{}
resp := nc.GenNatHoleResponse(session, "") resp := nc.GenNatHoleResponse(session, "")
log.Trace("send nat hole response to client") log.Trace("send nat hole response to client")
@@ -201,5 +208,5 @@ type NatHoleSession struct {
type NatHoleClientCfg struct { type NatHoleClientCfg struct {
Name string Name string
Sk string Sk string
SidCh chan string SidCh chan *SidRequest
} }

View File

@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
return PluginHttpProxy return PluginHttpProxy
} }
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := gnet.NewSharedConn(wrapConn) sc, rd := gnet.NewSharedConn(wrapConn)

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

@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
type Plugin interface { type Plugin interface {
Name() string Name() string
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
Close() error Close() error
} }

View File

@@ -53,7 +53,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
return return
} }
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
defer conn.Close() defer conn.Close()
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.Server.ServeConn(wrapConn) sp.Server.ServeConn(wrapConn)

View File

@@ -72,7 +72,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
return sp, nil return sp, nil
} }
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.l.PutConn(wrapConn) sp.l.PutConn(wrapConn)
} }

View File

@@ -53,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
return return
} }
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
if err != nil { if err != nil {
return return
} }
if len(extraBufToLocal) > 0 {
localConn.Write(extraBufToLocal)
}
frpIo.Join(localConn, conn) frpIo.Join(localConn, conn)
} }

View File

@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
default: default:
} }
} }
return
} }
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) { func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {

View File

@@ -44,7 +44,7 @@ for os in $os_all; do
mv ./frps_${os}_${arch} ${frp_path}/frps mv ./frps_${os}_${arch} ${frp_path}/frps
fi fi
cp ./LICENSE ${frp_path} cp ./LICENSE ${frp_path}
cp ./conf/* ${frp_path} cp -rf ./conf/* ${frp_path}
# packages # packages
cd ./packages cd ./packages

View File

@@ -21,7 +21,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
frpErr "github.com/fatedier/frp/models/errors" frpErr "github.com/fatedier/frp/models/errors"
@@ -129,11 +128,19 @@ type Control struct {
allShutdown *shutdown.Shutdown allShutdown *shutdown.Shutdown
mu sync.RWMutex mu sync.RWMutex
// Server configuration information
serverCfg config.ServerCommonConf
} }
func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager, func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control { 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{ return &Control{
rc: rc, rc: rc,
pxyManager: pxyManager, pxyManager: pxyManager,
@@ -142,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
loginMsg: loginMsg, loginMsg: loginMsg,
sendCh: make(chan msg.Message, 10), sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10), readCh: make(chan msg.Message, 10),
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10), workConnCh: make(chan net.Conn, poolCount+10),
proxies: make(map[string]proxy.Proxy), proxies: make(map[string]proxy.Proxy),
poolCount: loginMsg.PoolCount, poolCount: poolCount,
portsUsedNum: 0, portsUsedNum: 0,
lastPing: time.Now(), lastPing: time.Now(),
runId: loginMsg.RunId, runId: loginMsg.RunId,
@@ -153,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
writerShutdown: shutdown.New(), writerShutdown: shutdown.New(),
managerShutdown: shutdown.New(), managerShutdown: shutdown.New(),
allShutdown: shutdown.New(), allShutdown: shutdown.New(),
serverCfg: serverCfg,
} }
} }
@@ -161,7 +169,7 @@ func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{ loginRespMsg := &msg.LoginResp{
Version: version.Full(), Version: version.Full(),
RunId: ctl.runId, RunId: ctl.runId,
ServerUdpPort: g.GlbServerCfg.BindUdpPort, ServerUdpPort: ctl.serverCfg.BindUdpPort,
Error: "", Error: "",
} }
msg.WriteMsg(ctl.conn, loginRespMsg) msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -232,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
return return
} }
case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second): case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection") err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err) ctl.conn.Warn("%v", err)
return return
@@ -263,7 +271,7 @@ func (ctl *Control) writer() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done() defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token)) encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
if err != nil { if err != nil {
ctl.conn.Error("crypto new writer error: %v", err) ctl.conn.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start() ctl.allShutdown.Start()
@@ -293,7 +301,7 @@ func (ctl *Control) reader() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token)) encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
for { for {
if m, err := msg.ReadMsg(encReader); err != nil { if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF { if err == io.EOF {
@@ -301,6 +309,7 @@ func (ctl *Control) reader() {
return return
} else { } else {
ctl.conn.Warn("read error: %v", err) ctl.conn.Warn("read error: %v", err)
ctl.conn.Close()
return return
} }
} else { } else {
@@ -373,7 +382,7 @@ func (ctl *Control) manager() {
for { for {
select { select {
case <-heartbeat.C: case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second { if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout") ctl.conn.Warn("heartbeat timeout")
return return
} }
@@ -416,22 +425,22 @@ func (ctl *Control) manager() {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf var pxyConf config.ProxyConf
// Load configures from NewProxy message and check. // Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg) pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
if err != nil { if err != nil {
return return
} }
// NewProxy will return a interface Proxy. // NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here. // In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf) pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
if err != nil { if err != nil {
return remoteAddr, err return remoteAddr, err
} }
// Check ports used number in each client // Check ports used number in each client
if g.GlbServerCfg.MaxPortsPerClient > 0 { if ctl.serverCfg.MaxPortsPerClient > 0 {
ctl.mu.Lock() ctl.mu.Lock()
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) { if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
ctl.mu.Unlock() ctl.mu.Unlock()
err = fmt.Errorf("exceed the max_ports_per_client") err = fmt.Errorf("exceed the max_ports_per_client")
return return
@@ -477,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
return return
} }
if g.GlbServerCfg.MaxPortsPerClient > 0 { if ctl.serverCfg.MaxPortsPerClient > 0 {
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum() ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
} }
pxy.Close() pxy.Close()

View File

@@ -29,6 +29,9 @@ type ResourceController struct {
// Tcp Group Controller // Tcp Group Controller
TcpGroupCtl *group.TcpGroupCtl TcpGroupCtl *group.TcpGroupCtl
// HTTP Group Controller
HTTPGroupCtl *group.HTTPGroupController
// Manage all tcp ports // Manage all tcp ports
TcpPortManager *ports.PortManager TcpPortManager *ports.PortManager
@@ -38,7 +41,7 @@ type ResourceController struct {
// For http proxies, forwarding http requests // For http proxies, forwarding http requests
HttpReverseProxy *vhost.HttpReverseProxy HttpReverseProxy *vhost.HttpReverseProxy
// For https proxies, route requests to different clients by hostname and other infomation // For https proxies, route requests to different clients by hostname and other information
VhostHttpsMuxer *vhost.HttpsMuxer VhostHttpsMuxer *vhost.HttpsMuxer
// Controller for nat hole connections // Controller for nat hole connections

View File

@@ -21,7 +21,6 @@ import (
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
// url router // url router
router := mux.NewRouter() router := mux.NewRouter()
user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware) router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go // api, see dashboard_api.go

View File

@@ -18,7 +18,6 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -28,13 +27,11 @@ import (
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
type ServerInfoResp struct { type ServerInfoResp struct {
GeneralResponse
Version string `json:"version"` Version string `json:"version"`
BindPort int `json:"bind_port"` BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"` BindUdpPort int `json:"bind_udp_port"`
@@ -55,28 +52,28 @@ type ServerInfoResp struct {
// api/serverinfo // api/serverinfo
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ServerInfoResp
)
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, 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: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
cfg := &g.GlbServerCfg.ServerCommonConf
serverStats := svr.statsCollector.GetServer() serverStats := svr.statsCollector.GetServer()
res = ServerInfoResp{ svrResp := ServerInfoResp{
Version: version.Full(), Version: version.Full(),
BindPort: cfg.BindPort, BindPort: svr.cfg.BindPort,
BindUdpPort: cfg.BindUdpPort, BindUdpPort: svr.cfg.BindUdpPort,
VhostHttpPort: cfg.VhostHttpPort, VhostHttpPort: svr.cfg.VhostHttpPort,
VhostHttpsPort: cfg.VhostHttpsPort, VhostHttpsPort: svr.cfg.VhostHttpsPort,
KcpBindPort: cfg.KcpBindPort, KcpBindPort: svr.cfg.KcpBindPort,
SubdomainHost: cfg.SubDomainHost, SubdomainHost: svr.cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount, MaxPoolCount: svr.cfg.MaxPoolCount,
MaxPortsPerClient: cfg.MaxPortsPerClient, MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
HeartBeatTimeout: cfg.HeartBeatTimeout, HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn, TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut, TotalTrafficOut: serverStats.TotalTrafficOut,
@@ -85,8 +82,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
ProxyTypeCounts: serverStats.ProxyTypeCounts, ProxyTypeCounts: serverStats.ProxyTypeCounts,
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&svrResp)
w.Write(buf) res.Msg = string(buf)
} }
type BaseOutConf struct { type BaseOutConf struct {
@@ -155,31 +152,29 @@ type ProxyStatsInfo struct {
} }
type GetProxyInfoResp struct { type GetProxyInfoResp struct {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"` Proxies []*ProxyStatsInfo `json:"proxies"`
} }
// api/proxy/:type // api/proxy/:type
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyInfoResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
log.Info(r.URL.Path) w.WriteHeader(res.Code)
log.Info(r.URL.RawPath) if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Proxies = svr.getProxyStatsByType(proxyType) proxyInfoResp := GetProxyInfoResp{}
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
buf, _ = json.Marshal(&res)
w.Write(buf)
buf, _ := json.Marshal(&proxyInfoResp)
res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) { func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
@@ -215,8 +210,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
// Get proxy info by name. // Get proxy info by name.
type GetProxyStatsResp struct { type GetProxyStatsResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
Conf interface{} `json:"conf"` Conf interface{} `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"` TodayTrafficIn int64 `json:"today_traffic_in"`
@@ -229,45 +222,50 @@ type GetProxyStatsResp struct {
// api/proxy/:type/:name // api/proxy/:type/:name
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyStatsResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, 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: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res = svr.getProxyStatsByTypeAndName(proxyType, name) proxyStatsResp := GetProxyStatsResp{}
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
if res.Code != 200 {
return
}
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&proxyStatsResp)
w.Write(buf) res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) { func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
proxyInfo.Name = proxyName proxyInfo.Name = proxyName
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName) ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
if ps == nil { if ps == nil {
proxyInfo.Code = 1 code = 404
proxyInfo.Msg = "no proxy info found" msg = "no proxy info found"
} else { } else {
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok { if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
content, err := json.Marshal(pxy.GetConf()) content, err := json.Marshal(pxy.GetConf())
if err != nil { if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Conf = getConfByType(ps.Type) proxyInfo.Conf = getConfByType(ps.Type)
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Status = consts.Online proxyInfo.Status = consts.Online
@@ -279,6 +277,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
proxyInfo.CurConns = ps.CurConns proxyInfo.CurConns = ps.CurConns
proxyInfo.LastStartTime = ps.LastStartTime proxyInfo.LastStartTime = ps.LastStartTime
proxyInfo.LastCloseTime = ps.LastCloseTime proxyInfo.LastCloseTime = ps.LastCloseTime
code = 200
} }
return return
@@ -286,36 +285,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
// api/traffic/:name // api/traffic/:name
type GetProxyTrafficResp struct { type GetProxyTrafficResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
TrafficIn []int64 `json:"traffic_in"` TrafficIn []int64 `json:"traffic_in"`
TrafficOut []int64 `json:"traffic_out"` TrafficOut []int64 `json:"traffic_out"`
} }
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyTrafficResp
)
params := mux.Vars(r) params := mux.Vars(r)
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, 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: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Name = name trafficResp := GetProxyTrafficResp{}
trafficResp.Name = name
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name) proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
if proxyTrafficInfo == nil { if proxyTrafficInfo == nil {
res.Code = 1 res.Code = 404
res.Msg = "no proxy info found" res.Msg = "no proxy info found"
return
} else { } else {
res.TrafficIn = proxyTrafficInfo.TrafficIn trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
res.TrafficOut = proxyTrafficInfo.TrafficOut trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&trafficResp)
w.Write(buf) res.Msg = string(buf)
} }

View File

@@ -23,4 +23,5 @@ var (
ErrGroupParamsInvalid = errors.New("group params invalid") ErrGroupParamsInvalid = errors.New("group params invalid")
ErrListenerClosed = errors.New("group listener closed") ErrListenerClosed = errors.New("group listener closed")
ErrGroupDifferentPort = errors.New("group should have same remote port") 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)
}

View File

@@ -24,46 +24,47 @@ import (
gerr "github.com/fatedier/golib/errors" gerr "github.com/fatedier/golib/errors"
) )
type TcpGroupListener struct { // TcpGroupCtl manage all TcpGroups
groupName string type TcpGroupCtl struct {
group *TcpGroup groups map[string]*TcpGroup
addr net.Addr // portManager is used to manage port
closeCh chan struct{} portManager *ports.PortManager
mu sync.Mutex
} }
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener { // NewTcpGroupCtl return a new TcpGroupCtl
return &TcpGroupListener{ func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
groupName: name, return &TcpGroupCtl{
group: group, groups: make(map[string]*TcpGroup),
addr: addr, portManager: portManager,
closeCh: make(chan struct{}),
} }
} }
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) { // Listen is the wrapper for TcpGroup's Listen
var ok bool // If there are no group, we will create one here
select { func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
case <-ln.closeCh: addr string, port int) (l net.Listener, realPort int, err error) {
return nil, ErrListenerClosed
case c, ok = <-ln.group.Accept(): tgc.mu.Lock()
if !ok { tcpGroup, ok := tgc.groups[group]
return nil, ErrListenerClosed if !ok {
} tcpGroup = NewTcpGroup(tgc)
return c, nil tgc.groups[group] = tcpGroup
} }
tgc.mu.Unlock()
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
} }
func (ln *TcpGroupListener) Addr() net.Addr { // RemoveGroup remove TcpGroup from controller
return ln.addr func (tgc *TcpGroupCtl) RemoveGroup(group string) {
} tgc.mu.Lock()
defer tgc.mu.Unlock()
func (ln *TcpGroupListener) Close() (err error) { delete(tgc.groups, group)
close(ln.closeCh)
ln.group.CloseListener(ln)
return
} }
// TcpGroup route connections to different proxies
type TcpGroup struct { type TcpGroup struct {
group string group string
groupKey string groupKey string
@@ -79,6 +80,7 @@ type TcpGroup struct {
mu sync.Mutex mu sync.Mutex
} }
// NewTcpGroup return a new TcpGroup
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup { func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
return &TcpGroup{ return &TcpGroup{
lns: make([]*TcpGroupListener, 0), lns: make([]*TcpGroupListener, 0),
@@ -87,10 +89,14 @@ func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
} }
} }
// 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) { func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
tg.mu.Lock() tg.mu.Lock()
defer tg.mu.Unlock() defer tg.mu.Unlock()
if len(tg.lns) == 0 { if len(tg.lns) == 0 {
// the first listener, listen on the real address
realPort, err = tg.ctl.portManager.Acquire(proxyName, port) realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
if err != nil { if err != nil {
return return
@@ -114,6 +120,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
} }
go tg.worker() go tg.worker()
} else { } else {
// address and port in the same group must be equal
if tg.group != group || tg.addr != addr { if tg.group != group || tg.addr != addr {
err = ErrGroupParamsInvalid err = ErrGroupParamsInvalid
return return
@@ -133,6 +140,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
return return
} }
// worker is called when the real tcp listener has been created
func (tg *TcpGroup) worker() { func (tg *TcpGroup) worker() {
for { for {
c, err := tg.tcpLn.Accept() c, err := tg.tcpLn.Accept()
@@ -152,6 +160,7 @@ func (tg *TcpGroup) Accept() <-chan net.Conn {
return tg.acceptCh return tg.acceptCh
} }
// CloseListener remove the TcpGroupListener from the TcpGroup
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) { func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
tg.mu.Lock() tg.mu.Lock()
defer tg.mu.Unlock() defer tg.mu.Unlock()
@@ -169,36 +178,47 @@ func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
} }
} }
type TcpGroupCtl struct { // TcpGroupListener
groups map[string]*TcpGroup type TcpGroupListener struct {
groupName string
group *TcpGroup
portManager *ports.PortManager addr net.Addr
mu sync.Mutex closeCh chan struct{}
} }
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl { func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
return &TcpGroupCtl{ return &TcpGroupListener{
groups: make(map[string]*TcpGroup), groupName: name,
portManager: portManager, group: group,
addr: addr,
closeCh: make(chan struct{}),
} }
} }
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string, // Accept will accept connections from TcpGroup
addr string, port int) (l net.Listener, realPort int, err error) { func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
var ok bool
tgc.mu.Lock() select {
defer tgc.mu.Unlock() case <-ln.closeCh:
if tcpGroup, ok := tgc.groups[group]; ok { return nil, ErrListenerClosed
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port) case c, ok = <-ln.group.Accept():
} else { if !ok {
tcpGroup = NewTcpGroup(tgc) return nil, ErrListenerClosed
tgc.groups[group] = tcpGroup }
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port) return c, nil
} }
} }
func (tgc *TcpGroupCtl) RemoveGroup(group string) { func (ln *TcpGroupListener) Addr() net.Addr {
tgc.mu.Lock() return ln.addr
defer tgc.mu.Unlock() }
delete(tgc.groups, group)
// Close close the listener
func (ln *TcpGroupListener) Close() (err error) {
close(ln.closeCh)
// remove self from TcpGroup
ln.group.CloseListener(ln)
return
} }

View File

@@ -16,9 +16,9 @@ package proxy
import ( import (
"io" "io"
"net"
"strings" "strings"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server/stats" "github.com/fatedier/frp/server/stats"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
@@ -29,7 +29,7 @@ import (
) )
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
closeFuncs []func() closeFuncs []func()
@@ -49,40 +49,78 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
locations = []string{""} locations = []string{""}
} }
defer func() {
if err != nil {
pxy.Close()
}
}()
addrs := make([]string, 0) addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains { for _, domain := range pxy.cfg.CustomDomains {
if domain == "" {
continue
}
routeConfig.Domain = domain routeConfig.Domain = domain
for _, location := range locations { for _, location := range locations {
routeConfig.Location = location routeConfig.Location = location
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
if err != nil {
return
}
tmpDomain := routeConfig.Domain tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
pxy.closeFuncs = append(pxy.closeFuncs, func() { // handle group
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) if pxy.cfg.Group != "" {
}) err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) 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 != "" { if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
for _, location := range locations { for _, location := range locations {
routeConfig.Location = location routeConfig.Location = location
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
if err != nil {
return
}
tmpDomain := routeConfig.Domain tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
pxy.closeFuncs = append(pxy.closeFuncs, func() { // handle group
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) if pxy.cfg.Group != "" {
}) err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) 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, ",") remoteAddr = strings.Join(addrs, ",")
@@ -93,8 +131,14 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) { func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
tmpConn, errRet := pxy.GetWorkConnFromPool() 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 { if errRet != nil {
err = errRet err = errRet
return return
@@ -102,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
var rwc io.ReadWriteCloser = tmpConn var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption { if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token)) rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
if err != nil { if err != nil {
pxy.Error("create encryption stream error: %v", err) pxy.Error("create encryption stream error: %v", err)
return return

View File

@@ -17,22 +17,30 @@ package proxy
import ( import (
"strings" "strings"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost" "github.com/fatedier/frp/utils/vhost"
) )
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
} }
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
routeConfig := &vhost.VhostRouteConfig{} routeConfig := &vhost.VhostRouteConfig{}
defer func() {
if err != nil {
pxy.Close()
}
}()
addrs := make([]string, 0) addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains { for _, domain := range pxy.cfg.CustomDomains {
if domain == "" {
continue
}
routeConfig.Domain = domain routeConfig.Domain = domain
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil { if errRet != nil {
@@ -42,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
l.AddLogPrefix(pxy.name) l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l) pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort)) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
} }
if pxy.cfg.SubDomain != "" { if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -55,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
l.AddLogPrefix(pxy.name) l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l) pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort))) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
} }
pxy.startListenHandler(pxy, HandleUserTcpConnection) pxy.startListenHandler(pxy, HandleUserTcpConnection)

View File

@@ -17,9 +17,10 @@ package proxy
import ( import (
"fmt" "fmt"
"io" "io"
"net"
"strconv"
"sync" "sync"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/controller"
@@ -36,7 +37,7 @@ type Proxy interface {
Run() (remoteAddr string, err error) Run() (remoteAddr string, err error)
GetName() string GetName() string
GetConf() config.ProxyConf GetConf() config.ProxyConf
GetWorkConnFromPool() (workConn frpNet.Conn, err error) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
GetUsedPortsNum() int GetUsedPortsNum() int
Close() Close()
log.Logger log.Logger
@@ -50,6 +51,7 @@ type BaseProxy struct {
usedPortsNum int usedPortsNum int
poolCount int poolCount int
getWorkConnFn GetWorkConnFn getWorkConnFn GetWorkConnFn
serverCfg config.ServerCommonConf
mu sync.RWMutex mu sync.RWMutex
log.Logger log.Logger
@@ -70,7 +72,9 @@ func (pxy *BaseProxy) Close() {
} }
} }
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) { // GetWorkConnFromPool try to get a new work connections from pool
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
// try all connections from the pool // try all connections from the pool
for i := 0; i < pxy.poolCount+1; i++ { for i := 0; i < pxy.poolCount+1; i++ {
if workConn, err = pxy.getWorkConnFn(); err != nil { if workConn, err = pxy.getWorkConnFn(); err != nil {
@@ -80,8 +84,29 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
workConn.AddLogPrefix(pxy.GetName()) workConn.AddLogPrefix(pxy.GetName())
var (
srcAddr string
dstAddr string
srcPortStr string
dstPortStr string
srcPort int
dstPort int
)
if src != nil {
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
srcPort, _ = strconv.Atoi(srcPortStr)
}
if dst != nil {
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
dstPort, _ = strconv.Atoi(dstPortStr)
}
err := msg.WriteMsg(workConn, &msg.StartWorkConn{ err := msg.WriteMsg(workConn, &msg.StartWorkConn{
ProxyName: pxy.GetName(), ProxyName: pxy.GetName(),
SrcAddr: srcAddr,
SrcPort: uint16(srcPort),
DstAddr: dstAddr,
DstPort: uint16(dstPort),
}) })
if err != nil { if err != nil {
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
@@ -101,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
// startListenHandler start a goroutine handler for each listener. // startListenHandler start a goroutine handler for each listener.
// p: p will just be passed to handler(Proxy, frpNet.Conn). // 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. // handler: each proxy type can set different handler function to deal with connections accepted from listeners.
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) { func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
for _, listener := range pxy.listeners { for _, listener := range pxy.listeners {
go func(l frpNet.Listener) { go func(l frpNet.Listener) {
for { for {
@@ -113,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
return return
} }
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String()) pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
go handler(p, c, pxy.statsCollector) go handler(p, c, pxy.statsCollector, pxy.serverCfg)
} }
}(listener) }(listener)
} }
} }
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int, func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) { getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
basePxy := BaseProxy{ basePxy := BaseProxy{
name: pxyConf.GetBaseInfo().ProxyName, name: pxyConf.GetBaseInfo().ProxyName,
@@ -130,38 +155,39 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
poolCount: poolCount, poolCount: poolCount,
getWorkConnFn: getWorkConnFn, getWorkConnFn: getWorkConnFn,
Logger: log.NewPrefixLogger(runId), Logger: log.NewPrefixLogger(runId),
serverCfg: serverCfg,
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
default: default:
@@ -173,11 +199,11 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
// HandleUserTcpConnection is used for incoming tcp user connections. // HandleUserTcpConnection is used for incoming tcp user connections.
// It can be used for tcp, http, https type. // It can be used for tcp, http, https type.
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) { func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
defer userConn.Close() defer userConn.Close()
// try all connections from the pool // try all connections from the pool
workConn, err := pxy.GetWorkConnFromPool() workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
if err != nil { if err != nil {
return return
} }
@@ -186,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
var local io.ReadWriteCloser = workConn var local io.ReadWriteCloser = workConn
cfg := pxy.GetConf().GetBaseInfo() cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption { if cfg.UseEncryption {
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
if err != nil { if err != nil {
pxy.Error("create encryption stream error: %v", err) pxy.Error("create encryption stream error: %v", err)
return return

View File

@@ -19,7 +19,7 @@ import (
) )
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
} }

View File

@@ -17,13 +17,12 @@ package proxy
import ( import (
"fmt" "fmt"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
) )
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
realPort int realPort int
@@ -31,7 +30,7 @@ type TcpProxy struct {
func (pxy *TcpProxy) Run() (remoteAddr string, err error) { func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
if pxy.cfg.Group != "" { if pxy.cfg.Group != "" {
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort) l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
pxy.rc.TcpPortManager.Release(pxy.realPort) pxy.rc.TcpPortManager.Release(pxy.realPort)
} }
}() }()
listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort) listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

View File

@@ -20,7 +20,6 @@ import (
"net" "net"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp" "github.com/fatedier/frp/models/proto/udp"
@@ -30,7 +29,7 @@ import (
) )
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf
realPort int realPort int
@@ -67,7 +66,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realPort
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort)) addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@@ -160,7 +159,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
// Sleep a while for waiting control send the NewProxyResp to client. // Sleep a while for waiting control send the NewProxyResp to client.
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
for { for {
workConn, err := pxy.GetWorkConnFromPool() workConn, err := pxy.GetWorkConnFromPool(nil, nil)
if err != nil { if err != nil {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
// check if proxy is closed // check if proxy is closed

View File

@@ -24,7 +24,7 @@ import (
) )
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
closeCh chan struct{} closeCh chan struct{}
@@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
select { select {
case <-pxy.closeCh: case <-pxy.closeCh:
break break
case sid := <-sidCh: case sidRequest := <-sidCh:
workConn, errRet := pxy.GetWorkConnFromPool() sr := sidRequest
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
if errRet != nil { if errRet != nil {
continue continue
} }
m := &msg.NatHoleSid{ m := &msg.NatHoleSid{
Sid: sid, Sid: sr.Sid,
} }
errRet = msg.WriteMsg(workConn, m) errRet = msg.WriteMsg(workConn, m)
if errRet != nil { if errRet != nil {
pxy.Warn("write nat hole sid package error, %v", errRet) pxy.Warn("write nat hole sid package error, %v", errRet)
workConn.Close()
break
} }
go func() {
raw, errRet := msg.ReadMsg(workConn)
if errRet != nil {
pxy.Warn("read nat hole client ok package error: %v", errRet)
workConn.Close()
return
}
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
pxy.Warn("read nat hole client ok package format error")
workConn.Close()
return
}
select {
case sr.NotifyCh <- struct{}{}:
default:
}
}()
} }
} }
}() }()

View File

@@ -16,14 +16,20 @@ package server
import ( import (
"bytes" "bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/nathole" "github.com/fatedier/frp/models/nathole"
"github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/controller"
@@ -45,8 +51,6 @@ const (
connReadTimeout time.Duration = 10 * time.Second connReadTimeout time.Duration = 10 * time.Second
) )
var ServerService *Service
// Server service // Server service
type Service struct { type Service struct {
// Dispatch connections to different handlers listen on same port // Dispatch connections to different handlers listen on same port
@@ -61,21 +65,30 @@ type Service struct {
// Accept connections using websocket // Accept connections using websocket
websocketListener frpNet.Listener websocketListener frpNet.Listener
// Accept frp tls connections
tlsListener frpNet.Listener
// Manage all controllers // Manage all controllers
ctlManager *ControlManager ctlManager *ControlManager
// Manage all proxies // Manage all proxies
pxyManager *proxy.ProxyManager pxyManager *proxy.ProxyManager
// HTTP vhost router
httpVhostRouter *vhost.VhostRouters
// All resource managers and controllers // All resource managers and controllers
rc *controller.ResourceController rc *controller.ResourceController
// stats collector to store server and proxies stats info // stats collector to store server and proxies stats info
statsCollector stats.Collector statsCollector stats.Collector
tlsConfig *tls.Config
cfg config.ServerCommonConf
} }
func NewService() (svr *Service, err error) { func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
cfg := &g.GlbServerCfg.ServerCommonConf
svr = &Service{ svr = &Service{
ctlManager: NewControlManager(), ctlManager: NewControlManager(),
pxyManager: proxy.NewProxyManager(), pxyManager: proxy.NewProxyManager(),
@@ -84,17 +97,19 @@ func NewService() (svr *Service, err error) {
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts), TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
}, },
httpVhostRouter: vhost.NewVhostRouters(),
tlsConfig: generateTLSConfig(),
cfg: cfg,
} }
// Init group controller // Init group controller
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager) svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
// Init assets. // Init HTTP group controller
err = assets.Load(cfg.AssetsDir) svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err) // Init 404 not found page
return vhost.NotFoundPagePath = cfg.Custom404Page
}
var ( var (
httpMuxOn bool httpMuxOn bool
@@ -144,7 +159,7 @@ func NewService() (svr *Service, err error) {
if cfg.VhostHttpPort > 0 { if cfg.VhostHttpPort > 0 {
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{ rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout, ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
}) }, svr.httpVhostRouter)
svr.rc.HttpReverseProxy = rp svr.rc.HttpReverseProxy = rp
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort) address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
@@ -187,6 +202,12 @@ func NewService() (svr *Service, err error) {
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort) log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
} }
// frp tls listener
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
})
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
// Create nat hole controller. // Create nat hole controller.
if cfg.BindUdpPort > 0 { if cfg.BindUdpPort > 0 {
var nc *nathole.NatHoleController var nc *nathole.NatHoleController
@@ -203,6 +224,13 @@ func NewService() (svr *Service, err error) {
var statsEnable bool var statsEnable bool
// Create dashboard web server. // Create dashboard web server.
if cfg.DashboardPort > 0 { if cfg.DashboardPort > 0 {
// Init dashboard assets
err = assets.Load(cfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort) err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
if err != nil { if err != nil {
err = fmt.Errorf("Create dashboard web server error, %v", err) err = fmt.Errorf("Create dashboard web server error, %v", err)
@@ -220,11 +248,12 @@ func (svr *Service) Run() {
if svr.rc.NatHoleController != nil { if svr.rc.NatHoleController != nil {
go svr.rc.NatHoleController.Run() go svr.rc.NatHoleController.Run()
} }
if g.GlbServerCfg.KcpBindPort > 0 { if svr.cfg.KcpBindPort > 0 {
go svr.HandleListener(svr.kcpListener) go svr.HandleListener(svr.kcpListener)
} }
go svr.HandleListener(svr.websocketListener) go svr.HandleListener(svr.websocketListener)
go svr.HandleListener(svr.tlsListener)
svr.HandleListener(svr.listener) svr.HandleListener(svr.listener)
} }
@@ -238,6 +267,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
return return
} }
log.Trace("start check TLS connection...")
originConn := c
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
if err != nil {
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
originConn.Close()
continue
}
log.Trace("success check TLS connection")
// Start a new goroutine for dealing connections. // Start a new goroutine for dealing connections.
go func(frpConn frpNet.Conn) { go func(frpConn frpNet.Conn) {
dealFn := func(conn frpNet.Conn) { dealFn := func(conn frpNet.Conn) {
@@ -285,7 +324,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
} }
} }
if g.GlbServerCfg.TcpMux { if svr.cfg.TcpMux {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard fmuxCfg.LogOutput = ioutil.Discard
@@ -324,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
} }
// Check auth. // Check auth.
if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
err = fmt.Errorf("authorization failed") err = fmt.Errorf("authorization failed")
return return
} }
@@ -338,7 +377,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
} }
} }
ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg) ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil { if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDone() oldCtl.allShutdown.WaitDone()
@@ -373,3 +412,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression) newMsg.UseEncryption, newMsg.UseCompression)
} }
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
}

View File

@@ -127,6 +127,12 @@ custom_domains = test6.frp.com
host_header_rewrite = test6.frp.com host_header_rewrite = test6.frp.com
header_X-From-Where = frp header_X-From-Where = frp
[wildcard_http]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = *.frp1.com
[subhost01] [subhost01]
type = http type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1

View File

@@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10701", "-r", "20801", "-n", "tcp_test"}) "-l", "10701", "-r", "20801", "-n", "tcp_test"})
@@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err) assert.NoError(err)
@@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-l", "10702", "-r", "20802", "-n", "udp_test"}) "-l", "10702", "-r", "20802", "-n", "udp_test"})
@@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR) res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
assert.NoError(err) assert.NoError(err)
@@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer s.Stop() defer s.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test", c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"}) "-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
@@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer c.Stop() defer c.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "") code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
if assert.NoError(err) { if assert.NoError(err) {

View File

@@ -67,12 +67,26 @@ custom_domains = test2.com
health_check_type = http health_check_type = http
health_check_interval_s = 1 health_check_interval_s = 1
health_check_url = /health health_check_url = /health
[http3]
type = http
local_port = 15005
custom_domains = test.balancing.com
group = test-balancing
group_key = 123
[http4]
type = http
local_port = 15006
custom_domains = test.balancing.com
group = test-balancing
group_key = 123
` `
func TestHealthCheck(t *testing.T) { func TestHealthCheck(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// ****** start backgroud services ****** // ****** start background services ******
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1") echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
err := echoSvc1.Start() err := echoSvc1.Start()
if assert.NoError(err) { if assert.NoError(err) {
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
defer httpSvc2.Stop() defer httpSvc2.Stop()
} }
httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("http3"))
})
err = httpSvc3.Start()
if assert.NoError(err) {
defer httpSvc3.Stop()
}
httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("http4"))
})
err = httpSvc4.Start()
if assert.NoError(err) {
defer httpSvc4.Stop()
}
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
// ****** start frps and frpc ****** // ****** start frps and frpc ******
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
assert.NoError(err) assert.NoError(err)
assert.Equal(200, code) assert.Equal(200, code)
assert.Equal("http2", body) assert.Equal("http2", body)
// ****** load balancing type http ******
result = make([]string, 0)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
result = append(result, body)
code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
result = append(result, body)
assert.Contains(result, "http3")
assert.Contains(result, "http4")
} }

View File

@@ -182,6 +182,21 @@ func TestHttp(t *testing.T) {
assert.Equal("true", header.Get("X-Header-Set")) assert.Equal("true", header.Get("X-Header-Set"))
} }
// wildcard_http
// test.frp1.com match *.frp1.com
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
// new.test.frp1.com also match *.frp1.com
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
}
// subhost01 // subhost01
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "") code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
if assert.NoError(err) { if assert.NoError(err) {

View File

@@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
if assert.NoError(err) { if assert.NoError(err) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp // test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) {
// stop frpc // stop frpc
frpcProcess.Stop() frpcProcess.Stop()
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
// test tcp, expect failed // test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) {
if assert.NoError(err) { if assert.NoError(err) {
defer newFrpcProcess.Stop() defer newFrpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp // test tcp
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
@@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) {
// stop frps // stop frps
frpsProcess.Stop() frpsProcess.Stop()
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
// test tcp, expect failed // test tcp, expect failed
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) _, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

View File

@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath}) frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
@@ -102,7 +102,7 @@ func TestReload(t *testing.T) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp1 // test tcp1
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

View File

@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
defer frpsProcess.Stop() defer frpsProcess.Stop()
} }
time.Sleep(100 * time.Millisecond) time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath}) frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
err = frpcProcess.Start() err = frpcProcess.Start()
@@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) {
defer frpcProcess.Stop() defer frpcProcess.Stop()
} }
time.Sleep(250 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// test tcp1 // test tcp1
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)

188
tests/ci/tls_test.go Normal file
View File

@@ -0,0 +1,188 @@
package ci
import (
"os"
"testing"
"time"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
const FRPS_TLS_TCP_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_TCP_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = tcp
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTlsOverTCP(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
const FRPS_TLS_KCP_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
kcp_bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_KCP_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = kcp
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTLSOverKCP(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}
const FRPS_TLS_WS_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
token = 123456
`
const FRPC_TLS_WS_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
token = 123456
protocol = websocket
tls_enable = true
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTLSOverWebsocket(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}

View File

@@ -88,8 +88,10 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
return return
} }
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") || if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") { strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR)) w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
} else if strings.Contains(r.Host, "test3.frp.com") { } else if strings.Contains(r.Host, "test3.frp.com") {

View File

@@ -28,51 +28,51 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return status, err return status, err
} else { }
if resp.StatusCode != 200 { defer resp.Body.Close()
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode) if resp.StatusCode != 200 {
} return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
defer resp.Body.Close() }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return status, err return status, err
} }
allStatus := &client.StatusResp{} allStatus := &client.StatusResp{}
err = json.Unmarshal(body, &allStatus) err = json.Unmarshal(body, &allStatus)
if err != nil { if err != nil {
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body))) return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} }
for _, s := range allStatus.Tcp { for _, s := range allStatus.Tcp {
if s.Name == name { if s.Name == name {
return &s, nil return &s, nil
}
}
for _, s := range allStatus.Udp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Http {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Https {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Stcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Xtcp {
if s.Name == name {
return &s, nil
}
} }
} }
for _, s := range allStatus.Udp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Http {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Https {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Stcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Xtcp {
if s.Name == name {
return &s, nil
}
}
return status, errors.New("no proxy status found") return status, errors.New("no proxy status found")
} }
@@ -87,13 +87,13 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body)
} }
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
io.Copy(ioutil.Discard, resp.Body)
return nil return nil
} }

View File

@@ -20,6 +20,7 @@ import (
"github.com/fatedier/beego/logs" "github.com/fatedier/beego/logs"
) )
// Log is the under log object
var Log *logs.BeeLogger var Log *logs.BeeLogger
func init() { func init() {
@@ -28,21 +29,27 @@ func init() {
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
} }
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) { func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
SetLogFile(logWay, logFile, maxdays) SetLogFile(logWay, logFile, maxdays, disableLogColor)
SetLogLevel(logLevel) SetLogLevel(logLevel)
} }
// SetLogFile to configure log params
// logWay: file or console // logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64) { func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
if logWay == "console" { if logWay == "console" {
Log.SetLogger("console", "") params := ""
if disableLogColor {
params = fmt.Sprintf(`{"color": false}`)
}
Log.SetLogger("console", params)
} else { } else {
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays) params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
Log.SetLogger("file", params) Log.SetLogger("file", params)
} }
} }
// SetLogLevel set log level, default is warning
// value: error, warning, info, debug, trace // value: error, warning, info, debug, trace
func SetLogLevel(logLevel string) { func SetLogLevel(logLevel string) {
level := 4 // warning level := 4 // warning
@@ -85,7 +92,7 @@ func Trace(format string, v ...interface{}) {
Log.Trace(format, v...) Log.Trace(format, v...)
} }
// Logger // Logger is the log interface
type Logger interface { type Logger interface {
AddLogPrefix(string) AddLogPrefix(string)
GetPrefixStr() string GetPrefixStr() string

View File

@@ -15,6 +15,7 @@
package net package net
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -207,3 +208,17 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
return nil, fmt.Errorf("unsupport protocol: %s", protocol) return nil, fmt.Errorf("unsupport protocol: %s", protocol)
} }
} }
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
if err != nil {
return
}
if tlsConfig == nil {
return
}
c = WrapTLSClientConn(c, tlsConfig)
return
}

52
utils/net/tls.go Normal file
View File

@@ -0,0 +1,52 @@
// 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 net
import (
"crypto/tls"
"net"
"time"
gnet "github.com/fatedier/golib/net"
)
var (
FRP_TLS_HEAD_BYTE = 0x17
)
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
out = WrapConn(tls.Client(c, tlsConfig))
return
}
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
sc, r := gnet.NewSharedConnSize(c, 2)
buf := make([]byte, 1)
var n int
c.SetReadDeadline(time.Now().Add(timeout))
n, err = r.Read(buf)
c.SetReadDeadline(time.Time{})
if err != nil {
return
}
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
out = WrapConn(tls.Server(c, tlsConfig))
} else {
out = WrapConn(sc)
}
return
}

View File

@@ -31,6 +31,7 @@ type WebsocketListener struct {
httpMutex *http.ServeMux httpMutex *http.ServeMux
} }
// NewWebsocketListener to handle websocket connections
// ln: tcp listener for websocket connections // ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{ wl = &WebsocketListener{

View File

@@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.23.3" var version string = "0.29.0"
func Full() string { func Full() string {
return version return version

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com // Copyright 2017 fatedier, fatedier@gmail.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,221 +15,202 @@
package vhost package vhost
import ( import (
"bufio"
"bytes" "bytes"
"encoding/base64" "context"
"errors"
"fmt" "fmt"
"io" "log"
"net"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time" "time"
frpNet "github.com/fatedier/frp/utils/net" frpLog "github.com/fatedier/frp/utils/log"
gnet "github.com/fatedier/golib/net"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
) )
type HttpMuxer struct { var (
*VhostMuxer ErrNoDomain = errors.New("no such domain")
)
func getHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
host = strs[0]
} else {
host = addr
}
return
} }
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) { type HttpReverseProxyOptions struct {
reqInfoMap := make(map[string]string, 0) ResponseHeaderTimeoutS int64
sc, rd := gnet.NewSharedConn(c)
request, err := http.ReadRequest(bufio.NewReader(rd))
if err != nil {
return nil, reqInfoMap, err
}
// hostName
tmpArr := strings.Split(request.Host, ":")
reqInfoMap["Host"] = tmpArr[0]
reqInfoMap["Path"] = request.URL.Path
reqInfoMap["Scheme"] = request.URL.Scheme
// Authorization
authStr := request.Header.Get("Authorization")
if authStr != "" {
reqInfoMap["Authorization"] = authStr
}
request.Body.Close()
return frpNet.WrapConn(sc), reqInfoMap, nil
} }
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) { type HttpReverseProxy struct {
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout) proxy *ReverseProxy
return &HttpMuxer{mux}, err vhostRouter *VhostRouters
responseHeaderTimeout time.Duration
} }
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) { func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRouters) *HttpReverseProxy {
sc, rd := gnet.NewSharedConn(c) if option.ResponseHeaderTimeoutS <= 0 {
var buff []byte option.ResponseHeaderTimeoutS = 60
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
return nil, err
} }
err = sc.ResetBuf(buff) rp := &HttpReverseProxy{
return frpNet.WrapConn(sc), err responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
} vhostRouter: vhostRouter,
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
buf := pool.GetBuf(1024)
defer pool.PutBuf(buf)
var n int
n, err = request.Read(buf)
if err != nil {
return
} }
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP) proxy := &ReverseProxy{
return retBuffer, err Director: func(req *http.Request) {
} req.URL.Scheme = "http"
url := req.Context().Value("url").(string)
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) { oldHost := getHostFromAddr(req.Context().Value("host").(string))
tp := bytes.NewBuffer(org) host := rp.GetRealHost(oldHost, url)
// First line: GET /index.html HTTP/1.0 if host != "" {
var b []byte req.Host = host
if b, err = tp.ReadBytes('\n'); err != nil {
return nil, err
}
req := new(http.Request)
// we invoked ReadRequest in GetHttpHostname before, so we ignore error
req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b))
rawurl := req.RequestURI
// CONNECT www.google.com:443 HTTP/1.1
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
if justAuthority {
rawurl = "http://" + rawurl
}
req.URL, _ = url.ParseRequestURI(rawurl)
if justAuthority {
// Strip the bogus "http://" back off.
req.URL.Scheme = ""
}
// RFC2616: first case
// GET /index.html HTTP/1.1
// Host: www.google.com
if req.URL.Host == "" {
var changedBuf []byte
if rewriteHost != "" {
changedBuf, err = changeHostName(tp, rewriteHost)
}
buf := new(bytes.Buffer)
buf.Write(b)
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
if len(changedBuf) == 0 {
tp.WriteTo(buf)
} else {
buf.Write(changedBuf)
}
return buf.Bytes(), err
}
// RFC2616: second case
// GET http://www.google.com/index.html HTTP/1.1
// Host: doesntmatter
// In this case, any Host line is ignored.
if rewriteHost != "" {
hostPort := strings.Split(req.URL.Host, ":")
if len(hostPort) == 1 {
req.URL.Host = rewriteHost
} else if len(hostPort) == 2 {
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
}
}
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
buf := new(bytes.Buffer)
buf.WriteString(firstLine)
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
tp.WriteTo(buf)
return buf.Bytes(), err
}
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) {
retBuf := new(bytes.Buffer)
peek := buff.Bytes()
for len(peek) > 0 {
i := bytes.IndexByte(peek, '\n')
if i < 3 {
// Not present (-1) or found within the next few bytes,
// implying we're at the end ("\r\n\r\n" or "\n\n")
return nil, err
}
kv := peek[:i]
j := bytes.IndexByte(kv, ':')
if j < 0 {
return nil, fmt.Errorf("malformed MIME header line: " + string(kv))
}
if strings.Contains(strings.ToLower(string(kv[:j])), "host") {
var hostHeader string
portPos := bytes.IndexByte(kv[j+1:], ':')
if portPos == -1 {
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
} else {
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
} }
retBuf.WriteString(hostHeader) req.URL.Host = req.Host
peek = peek[i+1:]
break headers := rp.GetHeaders(oldHost, url)
} else { for k, v := range headers {
retBuf.Write(peek[:i]) req.Header.Set(k, v)
retBuf.WriteByte('\n') }
},
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string))
remote := ctx.Value("remote").(string)
return rp.CreateConnection(host, url, remote)
},
},
BufferPool: newWrapPool(),
ErrorLog: log.New(newWrapLogger(), "", 0),
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
frpLog.Warn("do http proxy request error: %v", err)
rw.WriteHeader(http.StatusNotFound)
rw.Write(getNotFoundPageContent())
},
}
rp.proxy = proxy
return rp
}
// Register register the route config to reverse proxy
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
if err != nil {
return err
}
return nil
}
// UnRegister unregister route config by domain and location
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
rp.vhostRouter.Del(domain, location)
}
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
vr, ok := rp.getVhost(domain, location)
if ok {
host = vr.payload.(*VhostRouteConfig).RewriteHost
}
return
}
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location)
if ok {
headers = vr.payload.(*VhostRouteConfig).Headers
}
return
}
// CreateConnection create a new connection by route config
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
if ok {
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
if fn != nil {
return fn(remoteAddr)
}
}
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
}
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location)
if ok {
checkUser := vr.payload.(*VhostRouteConfig).Username
checkPasswd := vr.payload.(*VhostRouteConfig).Password
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
return false
}
}
return true
}
// getVhost get vhost router by domain and location
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return
}
domainSplit := strings.Split(domain, ".")
if len(domainSplit) < 3 {
return nil, false
}
for {
if len(domainSplit) < 3 {
return nil, false
} }
peek = peek[i+1:] domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".")
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return vr, true
}
domainSplit = domainSplit[1:]
} }
retBuf.Write(peek) return
return retBuf.Bytes(), err
} }
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) { func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s := strings.SplitN(authorization, " ", 2) domain := getHostFromAddr(req.Host)
if len(s) != 2 { location := req.URL.Path
res := noAuthResponse() user, passwd, _ := req.BasicAuth()
res.Write(c) if !rp.CheckAuth(domain, location, user, passwd) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return return
} }
b, err := base64.StdEncoding.DecodeString(s[1]) rp.proxy.ServeHTTP(rw, req)
if err != nil {
return
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return
}
if pair[0] != userName || pair[1] != passWord {
return
}
return true, nil
} }
func noAuthResponse() *http.Response { type wrapPool struct{}
header := make(map[string][]string)
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`} func newWrapPool() *wrapPool { return &wrapPool{} }
res := &http.Response{
Status: "401 Not authorized", func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
StatusCode: 401,
Proto: "HTTP/1.1", func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
ProtoMajor: 1,
ProtoMinor: 1, type wrapLogger struct{}
Header: header,
} func newWrapLogger() *wrapLogger { return &wrapLogger{} }
return res
func (l *wrapLogger) Write(p []byte) (n int, err error) {
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
return len(p), nil
} }

View File

@@ -1,211 +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 vhost
import (
"bytes"
"context"
"errors"
"log"
"net"
"net/http"
"strings"
"sync"
"time"
frpLog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/golib/pool"
)
var (
ErrRouterConfigConflict = errors.New("router config conflict")
ErrNoDomain = errors.New("no such domain")
)
func getHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
host = strs[0]
} else {
host = addr
}
return
}
type HttpReverseProxyOptions struct {
ResponseHeaderTimeoutS int64
}
type HttpReverseProxy struct {
proxy *ReverseProxy
vhostRouter *VhostRouters
responseHeaderTimeout time.Duration
cfgMu sync.RWMutex
}
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
if option.ResponseHeaderTimeoutS <= 0 {
option.ResponseHeaderTimeoutS = 60
}
rp := &HttpReverseProxy{
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
vhostRouter: NewVhostRouters(),
}
proxy := &ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
url := req.Context().Value("url").(string)
oldHost := getHostFromAddr(req.Context().Value("host").(string))
host := rp.GetRealHost(oldHost, url)
if host != "" {
req.Host = host
}
req.URL.Host = req.Host
headers := rp.GetHeaders(oldHost, url)
for k, v := range headers {
req.Header.Set(k, v)
}
},
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string))
return rp.CreateConnection(host, url)
},
},
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string))
return rp.CreateConnection(host, url)
},
BufferPool: newWrapPool(),
ErrorLog: log.New(newWrapLogger(), "", 0),
}
rp.proxy = proxy
return rp
}
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
rp.cfgMu.Lock()
defer rp.cfgMu.Unlock()
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
if ok {
return ErrRouterConfigConflict
} else {
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
}
return nil
}
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
rp.cfgMu.Lock()
defer rp.cfgMu.Unlock()
rp.vhostRouter.Del(domain, location)
}
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
vr, ok := rp.getVhost(domain, location)
if ok {
host = vr.payload.(*VhostRouteConfig).RewriteHost
}
return
}
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location)
if ok {
headers = vr.payload.(*VhostRouteConfig).Headers
}
return
}
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
if ok {
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
if fn != nil {
return fn()
}
}
return nil, ErrNoDomain
}
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location)
if ok {
checkUser := vr.payload.(*VhostRouteConfig).Username
checkPasswd := vr.payload.(*VhostRouteConfig).Password
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
return false
}
}
return true
}
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
rp.cfgMu.RLock()
defer rp.cfgMu.RUnlock()
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return
}
domainSplit := strings.Split(domain, ".")
if len(domainSplit) < 3 {
return vr, false
}
domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".")
vr, ok = rp.vhostRouter.Get(domain, location)
return
}
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain := getHostFromAddr(req.Host)
location := req.URL.Path
user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
rp.proxy.ServeHTTP(rw, req)
}
type wrapPool struct{}
func newWrapPool() *wrapPool { return &wrapPool{} }
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
type wrapLogger struct{}
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
func (l *wrapLogger) Write(p []byte) (n int, err error) {
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
return len(p), nil
}

View File

@@ -15,13 +15,18 @@
package vhost package vhost
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings"
frpLog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
) )
var (
NotFoundPagePath = ""
)
const ( const (
NotFound = `<!DOCTYPE html> NotFound = `<!DOCTYPE html>
<html> <html>
@@ -46,10 +51,28 @@ Please try again later.</p>
` `
) )
func getNotFoundPageContent() []byte {
var (
buf []byte
err error
)
if NotFoundPagePath != "" {
buf, err = ioutil.ReadFile(NotFoundPagePath)
if err != nil {
frpLog.Warn("read custom 404 page error: %v", err)
buf = []byte(NotFound)
}
} else {
buf = []byte(NotFound)
}
return buf
}
func notFoundResponse() *http.Response { func notFoundResponse() *http.Response {
header := make(http.Header) header := make(http.Header)
header.Set("server", "frp/"+version.Full()) header.Set("server", "frp/"+version.Full())
header.Set("Content-Type", "text/html") header.Set("Content-Type", "text/html")
res := &http.Response{ res := &http.Response{
Status: "Not Found", Status: "Not Found",
StatusCode: 404, StatusCode: 404,
@@ -57,7 +80,21 @@ func notFoundResponse() *http.Response {
ProtoMajor: 1, ProtoMajor: 1,
ProtoMinor: 0, ProtoMinor: 0,
Header: header, Header: header,
Body: ioutil.NopCloser(strings.NewReader(NotFound)), Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
}
return res
}
func noAuthResponse() *http.Response {
header := make(map[string][]string)
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
res := &http.Response{
Status: "401 Not authorized",
StatusCode: 401,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: header,
} }
return res return res
} }

View File

@@ -8,6 +8,7 @@ package vhost
import ( import (
"context" "context"
"fmt"
"io" "io"
"log" "log"
"net" "net"
@@ -17,13 +18,9 @@ import (
"sync" "sync"
"time" "time"
frpIo "github.com/fatedier/golib/io" "golang.org/x/net/http/httpguts"
) )
// onExitFlushLoop is a callback set by tests to detect the state of the
// flushLoop() goroutine.
var onExitFlushLoop func()
// ReverseProxy is an HTTP Handler that takes an incoming request and // ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the // sends it to another server, proxying the response back to the
// client. // client.
@@ -44,12 +41,17 @@ type ReverseProxy struct {
// to flush to the client while copying the // to flush to the client while copying the
// response body. // response body.
// If zero, no periodic flushing is done. // If zero, no periodic flushing is done.
// A negative value means to flush immediately
// after each write to the client.
// The FlushInterval is ignored when ReverseProxy
// recognizes a response as a streaming response;
// for such responses, writes are flushed to the client
// immediately.
FlushInterval time.Duration FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors // ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request. // that occur when attempting to proxy the request.
// If nil, logging goes to os.Stderr via the log package's // If nil, logging is done via the log package's standard logger.
// standard logger.
ErrorLog *log.Logger ErrorLog *log.Logger
// BufferPool optionally specifies a buffer pool to // BufferPool optionally specifies a buffer pool to
@@ -57,12 +59,23 @@ type ReverseProxy struct {
// copying HTTP response bodies. // copying HTTP response bodies.
BufferPool BufferPool BufferPool BufferPool
// ModifyResponse is an optional function that // ModifyResponse is an optional function that modifies the
// modifies the Response from the backend. // Response from the backend. It is called if the backend
// If it returns an error, the proxy returns a StatusBadGateway error. // returns a response at all, with any HTTP status code.
// If the backend is unreachable, the optional ErrorHandler is
// called without any call to ModifyResponse.
//
// If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default
// implementation is used.
ModifyResponse func(*http.Response) error ModifyResponse func(*http.Response) error
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error) // ErrorHandler is an optional function that handles errors
// reaching the backend or errors from ModifyResponse.
//
// If nil, the default is to log the provided error and return
// a 502 Status Bad Gateway response.
ErrorHandler func(http.ResponseWriter, *http.Request, error)
} }
// A BufferPool is an interface for getting and returning temporary // A BufferPool is an interface for getting and returning temporary
@@ -118,18 +131,11 @@ func copyHeader(dst, src http.Header) {
} }
} }
func cloneHeader(h http.Header) http.Header {
h2 := make(http.Header, len(h))
for k, vv := range h {
vv2 := make([]string, len(vv))
copy(vv2, vv)
h2[k] = vv2
}
return h2
}
// Hop-by-hop headers. These are removed when sent to the backend. // Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html // As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{ var hopHeaders = []string{
"Connection", "Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
@@ -137,54 +143,38 @@ var hopHeaders = []string{
"Proxy-Authenticate", "Proxy-Authenticate",
"Proxy-Authorization", "Proxy-Authorization",
"Te", // canonicalized version of "TE" "Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522 "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding", "Transfer-Encoding",
"Upgrade", "Upgrade",
} }
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
}
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
if p.ErrorHandler != nil {
return p.ErrorHandler
}
return p.defaultErrorHandler
}
// modifyResponse conditionally runs the optional ModifyResponse hook
// and reports whether the request should proceed.
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
if p.ModifyResponse == nil {
return true
}
if err := p.ModifyResponse(res); err != nil {
res.Body.Close()
p.getErrorHandler()(rw, req, err)
return false
}
return true
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if IsWebsocketRequest(req) {
p.serveWebSocket(rw, req)
} else {
p.serveHTTP(rw, req)
}
}
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
if p.WebSocketDialContext == nil {
rw.WriteHeader(500)
return
}
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
if err != nil {
rw.WriteHeader(501)
return
}
defer targetConn.Close()
p.Director(req)
hijacker, ok := rw.(http.Hijacker)
if !ok {
rw.WriteHeader(500)
return
}
conn, _, errHijack := hijacker.Hijack()
if errHijack != nil {
rw.WriteHeader(500)
return
}
defer conn.Close()
req.Write(targetConn)
frpIo.Join(conn, targetConn)
}
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport transport := p.Transport
if transport == nil { if transport == nil {
transport = http.DefaultTransport transport = http.DefaultTransport
@@ -205,37 +195,49 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
}() }()
} }
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay outreq := req.WithContext(ctx)
if req.ContentLength == 0 { if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
} }
outreq.Header = cloneHeader(req.Header) // =============================
// Modified for frp
// Modify for frp
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path)) outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host)) outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "remote", req.RemoteAddr))
// =============================
p.Director(outreq) p.Director(outreq)
outreq.Close = false outreq.Close = false
// Remove hop-by-hop headers listed in the "Connection" header. reqUpType := upgradeType(outreq.Header)
// See RFC 2616, section 14.10. removeConnectionHeaders(outreq.Header)
if c := outreq.Header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
outreq.Header.Del(f)
}
}
}
// Remove hop-by-hop headers to the backend. Especially // Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent // important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us. // connection, regardless of what the client sent to us.
for _, h := range hopHeaders { for _, h := range hopHeaders {
if outreq.Header.Get(h) != "" { hv := outreq.Header.Get(h)
outreq.Header.Del(h) if hv == "" {
continue
} }
if h == "Te" && hv == "trailers" {
// Issue 21096: tell backend applications that
// care about trailer support that we support
// trailers. (We do, but we don't go out of
// our way to advertise that unless the
// incoming client request thought it was
// worth mentioning)
continue
}
outreq.Header.Del(h)
}
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
} }
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
@@ -250,32 +252,27 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
res, err := transport.RoundTrip(outreq) res, err := transport.RoundTrip(outreq)
if err != nil { if err != nil {
p.logf("http: proxy error: %v", err) p.getErrorHandler()(rw, outreq, err)
rw.WriteHeader(http.StatusNotFound)
rw.Write([]byte(NotFound))
return return
} }
// Remove hop-by-hop headers listed in the // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
// "Connection" header of the response. if res.StatusCode == http.StatusSwitchingProtocols {
if c := res.Header.Get("Connection"); c != "" { if !p.modifyResponse(rw, res, outreq) {
for _, f := range strings.Split(c, ",") { return
if f = strings.TrimSpace(f); f != "" {
res.Header.Del(f)
}
} }
p.handleUpgradeResponse(rw, outreq, res)
return
} }
removeConnectionHeaders(res.Header)
for _, h := range hopHeaders { for _, h := range hopHeaders {
res.Header.Del(h) res.Header.Del(h)
} }
if p.ModifyResponse != nil { if !p.modifyResponse(rw, res, outreq) {
if err := p.ModifyResponse(res); err != nil { return
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
return
}
} }
copyHeader(rw.Header(), res.Header) copyHeader(rw.Header(), res.Header)
@@ -292,6 +289,21 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
} }
rw.WriteHeader(res.StatusCode) rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
if err != nil {
defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
// on read error while copying body.
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) > 0 { if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer. // Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short // This prevents net/http from calculating the length for short
@@ -300,8 +312,6 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
fl.Flush() fl.Flush()
} }
} }
p.copyResponse(rw, res.Body)
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) == announcedTrailers { if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer) copyHeader(rw.Header(), res.Trailer)
@@ -316,16 +326,68 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
} }
} }
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { var inOurTests bool // whether we're in our own tests
if p.FlushInterval != 0 {
// shouldPanicOnCopyError reports whether the reverse proxy should
// panic with http.ErrAbortHandler. This is the right thing to do by
// default, but Go 1.10 and earlier did not, so existing unit tests
// weren't expecting panics. Only panic in our own tests, or when
// running under the HTTP server.
func shouldPanicOnCopyError(req *http.Request) bool {
if inOurTests {
// Our tests know to handle this panic.
return true
}
if req.Context().Value(http.ServerContextKey) != nil {
// We seem to be running under an HTTP server, so
// it'll recover the panic.
return true
}
// Otherwise act like Go 1.10 and earlier to not break
// existing tests.
return false
}
// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
// See RFC 7230, section 6.1
func removeConnectionHeaders(h http.Header) {
for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") {
if sf = strings.TrimSpace(sf); sf != "" {
h.Del(sf)
}
}
}
}
// flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response.
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration {
resCT := res.Header.Get("Content-Type")
// For Server-Sent Events responses, flush immediately.
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
if resCT == "text/event-stream" {
return -1 // negative means immediately
}
// TODO: more specific cases? e.g. res.ContentLength == -1?
return p.FlushInterval
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
if flushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok { if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{ mlw := &maxLatencyWriter{
dst: wf, dst: wf,
latency: p.FlushInterval, latency: flushInterval,
done: make(chan bool),
} }
go mlw.flushLoop()
defer mlw.stop() defer mlw.stop()
// set up initial timer so headers get flushed even if body writes are delayed
mlw.flushPending = true
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
dst = mlw dst = mlw
} }
} }
@@ -333,13 +395,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
var buf []byte var buf []byte
if p.BufferPool != nil { if p.BufferPool != nil {
buf = p.BufferPool.Get() buf = p.BufferPool.Get()
defer p.BufferPool.Put(buf)
} }
p.copyBuffer(dst, src, buf) _, err := p.copyBuffer(dst, src, buf)
if p.BufferPool != nil { return err
p.BufferPool.Put(buf)
}
} }
// copyBuffer returns any write errors or non-EOF read errors, and the amount
// of bytes written.
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) { func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 { if len(buf) == 0 {
buf = make([]byte, 32*1024) buf = make([]byte, 32*1024)
@@ -363,6 +426,9 @@ func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int
} }
} }
if rerr != nil { if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr return written, rerr
} }
} }
@@ -383,47 +449,115 @@ type writeFlusher interface {
type maxLatencyWriter struct { type maxLatencyWriter struct {
dst writeFlusher dst writeFlusher
latency time.Duration latency time.Duration // non-zero; negative means to flush immediately
mu sync.Mutex // protects Write + Flush mu sync.Mutex // protects t, flushPending, and dst.Flush
done chan bool t *time.Timer
flushPending bool
} }
func (m *maxLatencyWriter) Write(p []byte) (int, error) { func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
return m.dst.Write(p) n, err = m.dst.Write(p)
if m.latency < 0 {
m.dst.Flush()
return
}
if m.flushPending {
return
}
if m.t == nil {
m.t = time.AfterFunc(m.latency, m.delayedFlush)
} else {
m.t.Reset(m.latency)
}
m.flushPending = true
return
} }
func (m *maxLatencyWriter) flushLoop() { func (m *maxLatencyWriter) delayedFlush() {
t := time.NewTicker(m.latency) m.mu.Lock()
defer t.Stop() defer m.mu.Unlock()
for { if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
select { return
case <-m.done: }
if onExitFlushLoop != nil { m.dst.Flush()
onExitFlushLoop() m.flushPending = false
} }
return
case <-t.C: func (m *maxLatencyWriter) stop() {
m.mu.Lock() m.mu.Lock()
m.dst.Flush() defer m.mu.Unlock()
m.mu.Unlock() m.flushPending = false
} if m.t != nil {
m.t.Stop()
} }
} }
func (m *maxLatencyWriter) stop() { m.done <- true } func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
func IsWebsocketRequest(req *http.Request) bool { return ""
containsHeader := func(name, value string) bool {
items := strings.Split(req.Header.Get(name), ",")
for _, item := range items {
if value == strings.ToLower(strings.TrimSpace(item)) {
return true
}
}
return false
} }
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket") return strings.ToLower(h.Get("Upgrade"))
}
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if reqUpType != resUpType {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
return
}
copyHeader(res.Header, rw.Header())
hj, ok := rw.(http.Hijacker)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return
}
defer backConn.Close()
conn, brw, err := hj.Hijack()
if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return
}
defer conn.Close()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
return
}
if err := brw.Flush(); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
return
}
errc := make(chan error, 1)
spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
<-errc
return
}
// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
user, backend io.ReadWriter
}
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend)
errc <- err
}
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user)
errc <- err
} }

View File

@@ -1,11 +1,16 @@
package vhost package vhost
import ( import (
"errors"
"sort" "sort"
"strings" "strings"
"sync" "sync"
) )
var (
ErrRouterConfigConflict = errors.New("router config conflict")
)
type VhostRouters struct { type VhostRouters struct {
RouterByDomain map[string][]*VhostRouter RouterByDomain map[string][]*VhostRouter
mutex sync.RWMutex mutex sync.RWMutex
@@ -24,10 +29,14 @@ func NewVhostRouters() *VhostRouters {
} }
} }
func (r *VhostRouters) Add(domain, location string, payload interface{}) { func (r *VhostRouters) Add(domain, location string, payload interface{}) error {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
if _, exist := r.exist(domain, location); exist {
return ErrRouterConfigConflict
}
vrs, found := r.RouterByDomain[domain] vrs, found := r.RouterByDomain[domain]
if !found { if !found {
vrs = make([]*VhostRouter, 0, 1) vrs = make([]*VhostRouter, 0, 1)
@@ -42,6 +51,7 @@ func (r *VhostRouters) Add(domain, location string, payload interface{}) {
sort.Sort(sort.Reverse(ByLocation(vrs))) sort.Sort(sort.Reverse(ByLocation(vrs)))
r.RouterByDomain[domain] = vrs r.RouterByDomain[domain] = vrs
return nil
} }
func (r *VhostRouters) Del(domain, location string) { func (r *VhostRouters) Del(domain, location string) {
@@ -80,10 +90,7 @@ func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {
return return
} }
func (r *VhostRouters) Exist(host, path string) (vr *VhostRouter, exist bool) { func (r *VhostRouters) exist(host, path string) (vr *VhostRouter, exist bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
vrs, found := r.RouterByDomain[host] vrs, found := r.RouterByDomain[host]
if !found { if !found {
return return

View File

@@ -15,7 +15,6 @@ package vhost
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"time" "time"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -35,7 +34,6 @@ type VhostMuxer struct {
authFunc httpAuthFunc authFunc httpAuthFunc
rewriteFunc hostRewriteFunc rewriteFunc hostRewriteFunc
registryRouter *VhostRouters registryRouter *VhostRouters
mutex sync.RWMutex
} }
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) { func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
@@ -51,8 +49,9 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
return mux, nil return mux, nil
} }
type CreateConnFunc func() (frpNet.Conn, error) type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
// VhostRouteConfig is the params used to match HTTP requests
type VhostRouteConfig struct { type VhostRouteConfig struct {
Domain string Domain string
Location string Location string
@@ -67,14 +66,6 @@ type VhostRouteConfig struct {
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil // listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
// then rewrite the host header to rewriteHost // then rewrite the host header to rewriteHost
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) { func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
v.mutex.Lock()
defer v.mutex.Unlock()
_, ok := v.registryRouter.Exist(cfg.Domain, cfg.Location)
if ok {
return nil, fmt.Errorf("hostname [%s] location [%s] is already registered", cfg.Domain, cfg.Location)
}
l = &Listener{ l = &Listener{
name: cfg.Domain, name: cfg.Domain,
location: cfg.Location, location: cfg.Location,
@@ -85,14 +76,14 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
accept: make(chan frpNet.Conn), accept: make(chan frpNet.Conn),
Logger: log.NewPrefixLogger(""), Logger: log.NewPrefixLogger(""),
} }
v.registryRouter.Add(cfg.Domain, cfg.Location, l) err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
if err != nil {
return
}
return l, nil return l, nil
} }
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) { func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
v.mutex.RLock()
defer v.mutex.RUnlock()
// first we check the full hostname // first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com // if not exist, then check the wildcard_domain such as *.example.com
vr, found := v.registryRouter.Get(name, path) vr, found := v.registryRouter.Get(name, path)
@@ -102,17 +93,24 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
domainSplit := strings.Split(name, ".") domainSplit := strings.Split(name, ".")
if len(domainSplit) < 3 { if len(domainSplit) < 3 {
return l, false
}
domainSplit[0] = "*"
name = strings.Join(domainSplit, ".")
vr, found = v.registryRouter.Get(name, path)
if !found {
return return
} }
return vr.payload.(*Listener), true for {
if len(domainSplit) < 3 {
return
}
domainSplit[0] = "*"
name = strings.Join(domainSplit, ".")
vr, found = v.registryRouter.Get(name, path)
if found {
return vr.payload.(*Listener), true
}
domainSplit = domainSplit[1:]
}
return
} }
func (v *VhostMuxer) run() { func (v *VhostMuxer) run() {

View File

@@ -1,6 +1,8 @@
language: go language: go
go: go:
- 1.9 - 1.9.x
- 1.10.x
- 1.11.x
before_install: before_install:
- go get -t -v ./... - go get -t -v ./...

View File

@@ -20,24 +20,21 @@
**kcp-go** is a **Production-Grade Reliable-UDP** library for [golang](https://golang.org/). **kcp-go** is a **Production-Grade Reliable-UDP** library for [golang](https://golang.org/).
It provides **fast, ordered and error-checked** delivery of streams over **UDP** packets, has been well tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) are running with **kcp-go** at present, including applications like **online games, live broadcasting, file synchronization and network acceleration**. This library intents to provide a **smooth, resilient, ordered, error-checked and anonymous** delivery of streams over **UDP** packets, it has been battle-tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) have deployed **kcp-go** powered program in a variety of forms like **online games, live broadcasting, file synchronization and network acceleration**.
[Lastest Release](https://github.com/xtaci/kcp-go/releases) [Lastest Release](https://github.com/xtaci/kcp-go/releases)
## Features ## Features
1. Optimized for **Realtime Online Games, Audio/Video Streaming and Latency-Sensitive Distributed Consensus**. 1. Designed for **Latency-sensitive** scenarios.
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with language specific optimizations.
1. **Cache friendly** and **Memory optimized** design, offers extremely **High Performance** core. 1. **Cache friendly** and **Memory optimized** design, offers extremely **High Performance** core.
1. Handles **>5K concurrent connections** on a single commodity server. 1. Handles **>5K concurrent connections** on a single commodity server.
1. Compatible with [net.Conn](https://golang.org/pkg/net/#Conn) and [net.Listener](https://golang.org/pkg/net/#Listener), a drop-in replacement for [net.TCPConn](https://golang.org/pkg/net/#TCPConn). 1. Compatible with [net.Conn](https://golang.org/pkg/net/#Conn) and [net.Listener](https://golang.org/pkg/net/#Listener), a drop-in replacement for [net.TCPConn](https://golang.org/pkg/net/#TCPConn).
1. [FEC(Forward Error Correction)](https://en.wikipedia.org/wiki/Forward_error_correction) Support with [Reed-Solomon Codes](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction) 1. [FEC(Forward Error Correction)](https://en.wikipedia.org/wiki/Forward_error_correction) Support with [Reed-Solomon Codes](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode. 1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode, which generates completely anonymous packet.
1. **Fixed number of goroutines** created for the entire server application, minimized goroutine context switch. 1. Only **A fixed number of goroutines** will be created for the entire server application, costs in **context switch** between goroutines have been taken into consideration.
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with various improvements.
## Conventions 1. Platform-dependent optimizations: [sendmmsg](http://man7.org/linux/man-pages/man2/sendmmsg.2.html) and [recvmmsg](http://man7.org/linux/man-pages/man2/recvmmsg.2.html) were expoloited for linux.
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
## Documentation ## Documentation
@@ -47,6 +44,24 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
<img src="frame.png" alt="Frame Format" height="109px" /> <img src="frame.png" alt="Frame Format" height="109px" />
```
NONCE:
16bytes cryptographically secure random number, nonce changes for every packet.
CRC32:
CRC-32 checksum of data using the IEEE polynomial
FEC TYPE:
typeData = 0xF1
typeParity = 0xF2
FEC SEQID:
monotonically increasing in range: [0, (0xffffffff/shardSize) * shardSize - 1]
SIZE:
The size of KCP frame plus 2
```
``` ```
+-----------------+ +-----------------+
| SESSION | | SESSION |
@@ -69,58 +84,69 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
``` ```
## Usage ## Examples
Client: [full demo](https://github.com/xtaci/kcptun/blob/master/client/main.go) 1. [simple examples](https://github.com/xtaci/kcp-go/tree/master/examples)
```go 2. [kcptun client](https://github.com/xtaci/kcptun/blob/master/client/main.go)
kcpconn, err := kcp.DialWithOptions("192.168.0.1:10000", nil, 10, 3) 3. [kcptun server](https://github.com/xtaci/kcptun/blob/master/server/main.go)
```
Server: [full demo](https://github.com/xtaci/kcptun/blob/master/server/main.go)
```go
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
```
## Performance ## Benchmark
``` ```
Model Name: MacBook Pro Model Name: MacBook Pro
Model Identifier: MacBookPro12,1 Model Identifier: MacBookPro14,1
Processor Name: Intel Core i5 Processor Name: Intel Core i5
Processor Speed: 2.7 GHz Processor Speed: 3.1 GHz
Number of Processors: 1 Number of Processors: 1
Total Number of Cores: 2 Total Number of Cores: 2
L2 Cache (per Core): 256 KB L2 Cache (per Core): 256 KB
L3 Cache: 3 MB L3 Cache: 4 MB
Memory: 8 GB Memory: 8 GB
``` ```
``` ```
$ go test -v -run=^$ -bench . $ go test -v -run=^$ -bench .
beginning tests, encryption:salsa20, fec:10/3 beginning tests, encryption:salsa20, fec:10/3
BenchmarkAES128-4 200000 8256 ns/op 363.33 MB/s 0 B/op 0 allocs/op goos: darwin
BenchmarkAES192-4 200000 9153 ns/op 327.74 MB/s 0 B/op 0 allocs/op goarch: amd64
BenchmarkAES256-4 200000 10079 ns/op 297.64 MB/s 0 B/op 0 allocs/op pkg: github.com/xtaci/kcp-go
BenchmarkTEA-4 100000 18643 ns/op 160.91 MB/s 0 B/op 0 allocs/op BenchmarkSM4-4 50000 32180 ns/op 93.23 MB/s 0 B/op 0 allocs/op
BenchmarkXOR-4 5000000 316 ns/op 9486.46 MB/s 0 B/op 0 allocs/op BenchmarkAES128-4 500000 3285 ns/op 913.21 MB/s 0 B/op 0 allocs/op
BenchmarkBlowfish-4 50000 35643 ns/op 84.17 MB/s 0 B/op 0 allocs/op BenchmarkAES192-4 300000 3623 ns/op 827.85 MB/s 0 B/op 0 allocs/op
BenchmarkNone-4 30000000 56.2 ns/op 53371.83 MB/s 0 B/op 0 allocs/op BenchmarkAES256-4 300000 3874 ns/op 774.20 MB/s 0 B/op 0 allocs/op
BenchmarkCast5-4 30000 44744 ns/op 67.05 MB/s 0 B/op 0 allocs/op BenchmarkTEA-4 100000 15384 ns/op 195.00 MB/s 0 B/op 0 allocs/op
Benchmark3DES-4 2000 639839 ns/op 4.69 MB/s 2 B/op 0 allocs/op BenchmarkXOR-4 20000000 89.9 ns/op 33372.00 MB/s 0 B/op 0 allocs/op
BenchmarkTwofish-4 30000 43368 ns/op 69.17 MB/s 0 B/op 0 allocs/op BenchmarkBlowfish-4 50000 26927 ns/op 111.41 MB/s 0 B/op 0 allocs/op
BenchmarkXTEA-4 30000 57673 ns/op 52.02 MB/s 0 B/op 0 allocs/op BenchmarkNone-4 30000000 45.7 ns/op 65597.94 MB/s 0 B/op 0 allocs/op
BenchmarkSalsa20-4 300000 3917 ns/op 765.80 MB/s 0 B/op 0 allocs/op BenchmarkCast5-4 50000 34258 ns/op 87.57 MB/s 0 B/op 0 allocs/op
BenchmarkFlush-4 10000000 226 ns/op 0 B/op 0 allocs/op Benchmark3DES-4 10000 117149 ns/op 25.61 MB/s 0 B/op 0 allocs/op
BenchmarkEchoSpeed4K-4 5000 300030 ns/op 13.65 MB/s 5672 B/op 177 allocs/op BenchmarkTwofish-4 50000 33538 ns/op 89.45 MB/s 0 B/op 0 allocs/op
BenchmarkEchoSpeed64K-4 500 3202335 ns/op 20.47 MB/s 73295 B/op 2198 allocs/op BenchmarkXTEA-4 30000 45666 ns/op 65.69 MB/s 0 B/op 0 allocs/op
BenchmarkEchoSpeed512K-4 50 24926924 ns/op 21.03 MB/s 659339 B/op 17602 allocs/op BenchmarkSalsa20-4 500000 3308 ns/op 906.76 MB/s 0 B/op 0 allocs/op
BenchmarkEchoSpeed1M-4 20 64857821 ns/op 16.17 MB/s 1772437 B/op 42869 allocs/op BenchmarkCRC32-4 20000000 65.2 ns/op 15712.43 MB/s
BenchmarkSinkSpeed4K-4 30000 50230 ns/op 81.54 MB/s 2058 B/op 48 allocs/op BenchmarkCsprngSystem-4 1000000 1150 ns/op 13.91 MB/s
BenchmarkSinkSpeed64K-4 2000 648718 ns/op 101.02 MB/s 31165 B/op 687 allocs/op BenchmarkCsprngMD5-4 10000000 145 ns/op 110.26 MB/s
BenchmarkSinkSpeed256K-4 300 4635905 ns/op 113.09 MB/s 286229 B/op 5516 allocs/op BenchmarkCsprngSHA1-4 10000000 158 ns/op 126.54 MB/s
BenchmarkSinkSpeed1M-4 200 9566933 ns/op 109.60 MB/s 463771 B/op 10701 allocs/op BenchmarkCsprngNonceMD5-4 10000000 153 ns/op 104.22 MB/s
BenchmarkCsprngNonceAES128-4 100000000 19.1 ns/op 837.81 MB/s
BenchmarkFECDecode-4 1000000 1119 ns/op 1339.61 MB/s 1606 B/op 2 allocs/op
BenchmarkFECEncode-4 2000000 832 ns/op 1801.83 MB/s 17 B/op 0 allocs/op
BenchmarkFlush-4 5000000 272 ns/op 0 B/op 0 allocs/op
BenchmarkEchoSpeed4K-4 5000 259617 ns/op 15.78 MB/s 5451 B/op 149 allocs/op
BenchmarkEchoSpeed64K-4 1000 1706084 ns/op 38.41 MB/s 56002 B/op 1604 allocs/op
BenchmarkEchoSpeed512K-4 100 14345505 ns/op 36.55 MB/s 482597 B/op 13045 allocs/op
BenchmarkEchoSpeed1M-4 30 34859104 ns/op 30.08 MB/s 1143773 B/op 27186 allocs/op
BenchmarkSinkSpeed4K-4 50000 31369 ns/op 130.57 MB/s 1566 B/op 30 allocs/op
BenchmarkSinkSpeed64K-4 5000 329065 ns/op 199.16 MB/s 21529 B/op 453 allocs/op
BenchmarkSinkSpeed256K-4 500 2373354 ns/op 220.91 MB/s 166332 B/op 3554 allocs/op
BenchmarkSinkSpeed1M-4 300 5117927 ns/op 204.88 MB/s 310378 B/op 6988 allocs/op
PASS PASS
ok _/Users/xtaci/.godeps/src/github.com/xtaci/kcp-go 39.689s ok github.com/xtaci/kcp-go 50.349s
``` ```
## Design Considerations
## Typical Flame Graph
![Flame Graph in kcptun](flame.png)
## Key Design Considerations
1. slice vs. container/list 1. slice vs. container/list
@@ -139,7 +165,9 @@ List structure introduces **heavy cache misses** compared to slice which owns be
2. Timing accuracy vs. syscall clock_gettime 2. Timing accuracy vs. syscall clock_gettime
Timing is **critical** to **RTT estimator**, inaccurate timing introduces false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz), the benchmark for time.Now(): Timing is **critical** to **RTT estimator**, inaccurate timing leads to false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz).
The benchmark for time.Now() lies here:
https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
@@ -147,14 +175,37 @@ https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
BenchmarkNow-4 100000000 15.6 ns/op BenchmarkNow-4 100000000 15.6 ns/op
``` ```
In kcp-go, after each `kcp.output()` function call, current time will be updated upon return, and each `kcp.flush()` will get current time once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(no packet needs to be sent by `kcp.output()`), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**. In kcp-go, after each `kcp.output()` function call, current clock time will be updated upon return, and for a single `kcp.flush()` operation, current time will be queried from system once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(a fixed cost while no packet needs to be sent), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
3. Memory management
## Tuning Primary memory allocation are done from a global buffer pool xmit.Buf, in kcp-go, when we need to allocate some bytes, we can get from that pool, and a fixed-capacity 1500 bytes(mtuLimit) will be returned, the rx queue, tx queue and fec queue all receive bytes from there, and they will return the bytes to the pool after using to prevent unnecessary zer0ing of bytes. The pool mechanism maintained a high watermark for slice objects, these in-flight objects from the pool will survive from the perodical garbage collection, meanwhile the pool kept the ability to return the memory to runtime if in idle.
Q: I'm handling >5K connections on my server. the CPU utilization is high. 4. Information security
A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load. kcp-go is shipped with builtin packet encryption powered by various block encryption algorithms and works in [Cipher Feedback Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_(CFB)), for each packet to be sent, the encryption process will start from encrypting a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) from the [system entropy](https://en.wikipedia.org/wiki//dev/random), so encryption to same plaintexts never leads to a same ciphertexts thereafter.
The contents of the packets are completely anonymous with encryption, including the headers(FEC,KCP), checksums and contents. Note that, no matter which encryption method you choose on you upper layer, if you disable encryption, the transmit will be insecure somehow, since the header is ***PLAINTEXT*** to everyone it would be susceptible to header tampering, such as jamming the *sliding window size*, *round-trip time*, *FEC property* and *checksums*. ```AES-128``` is suggested for minimal encryption since modern CPUs are shipped with [AES-NI](https://en.wikipedia.org/wiki/AES_instruction_set) instructions and performs even better than `salsa20`(check the table above).
Other possible attacks to kcp-go includes: a) [traffic analysis](https://en.wikipedia.org/wiki/Traffic_analysis), dataflow on specific websites may have pattern while interchanging data, but this type of eavesdropping has been mitigated by adapting [smux](https://github.com/xtaci/smux) to mix data streams so as to introduce noises, perfect solution to this has not appeared yet, theroretically by shuffling/mixing messages on larger scale network may mitigate this problem. b) [replay attack](https://en.wikipedia.org/wiki/Replay_attack), since the asymmetrical encryption has not been introduced into kcp-go for some reason, capturing the packets and replay them on a different machine is possible, (notice: hijacking the session and decrypting the contents is still *impossible*), so upper layers should contain a asymmetrical encryption system to guarantee the authenticity of each message(to process message exactly once), such as HTTPS/OpenSSL/LibreSSL, only by signing the requests with private keys can eliminate this type of attack.
## Connection Termination
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
## FAQ
Q: I'm handling >5K connections on my server, the CPU utilization is so high.
A: A standalone `agent` or `gate` server for running kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements(timing) which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load, but lower the performance.
Q: When should I enable FEC?
A: Forward error correction is critical to long-distance transmission, because a packet loss will lead to a huge penalty in time. And for the complicated packet routing network in modern world, round-trip time based loss check will not always be efficient, the big deviation of RTT samples in the long way usually leads to a larger RTO value in typical rtt estimator, which in other words, slows down the transmission.
Q: Should I enable encryption?
A: Yes, for the safety of protocol, even if the upper layer has encrypted.
## Who is using this? ## Who is using this?
@@ -163,10 +214,9 @@ A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for C
3. https://github.com/smallnest/rpcx -- A RPC service framework based on net/rpc like alibaba Dubbo and weibo Motan. 3. https://github.com/smallnest/rpcx -- A RPC service framework based on net/rpc like alibaba Dubbo and weibo Motan.
4. https://github.com/gonet2/agent -- A gateway for games with stream multiplexing. 4. https://github.com/gonet2/agent -- A gateway for games with stream multiplexing.
5. https://github.com/syncthing/syncthing -- Open Source Continuous File Synchronization. 5. https://github.com/syncthing/syncthing -- Open Source Continuous File Synchronization.
6. https://play.google.com/store/apps/details?id=com.k17game.k3 -- Battle Zone - Earth 2048, a world-wide strategy game.
## Links ## Links
1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++ 1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++
2. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol 2. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol
3. https://github.com/templexxx/reedsolomon -- Reed-Solomon Erasure Coding in Go 3. https://github.com/klauspost/reedsolomon -- Reed-Solomon Erasure Coding in Go

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