Compare commits

...

35 Commits

Author SHA1 Message Date
fatedier
edb97abf50 Merge pull request #217 from fatedier/dev
bump version to 0.9.2
2017-01-08 09:22:28 -06:00
fatedier
0c10279deb doc: add new contributor 2017-01-08 23:18:25 +08:00
fatedier
1f49510e3e udp proxy: fix reconnect error, fix #209 2017-01-05 23:09:17 +08:00
fatedier
1868b3bafb Merge pull request #215 from bingtianbaihua/dev
modified readme.md
2017-01-05 07:39:15 -06:00
bingtianbaihua
a23521885c modified readme.md 2017-01-05 20:04:26 +08:00
fatedier
c80dcd050d update default value of heartbeat_interval and heartbeat_timeout 2017-01-04 23:09:28 +08:00
fatedier
043ab62587 build: update Makefile.cross-compiles 2017-01-04 22:56:08 +08:00
fatedier
a8969b1901 Merge pull request #207 from bingtianbaihua/master
added heartbeat conf
2016-12-30 04:01:02 -06:00
bingtianbaihua
e26285eefc added heartbeat conf 2016-12-30 17:47:34 +08:00
fatedier
299bd7b5cb frps: fix panic caused by frps closing the nil channel, fix #205 2016-12-29 23:49:39 +08:00
fatedier
90d1384bf7 frps: update 2016-12-28 00:56:55 +08:00
fatedier
a5434e31b7 doc: update 2016-12-28 00:40:45 +08:00
fatedier
044bb692dc Merge pull request #201 from fatedier/dev
bump version to 0.9.1
2016-12-27 10:35:14 -06:00
fatedier
34b98dde52 Merge pull request #200 from fatedier/doc
doc: support url routing
2016-12-27 10:32:06 -06:00
fatedier
020f786bf5 doc: support url routing 2016-12-28 00:26:50 +08:00
fatedier
cdcc1240ec vhost: fix a wrong usage of sort.Reverse 2016-12-27 02:52:32 +08:00
fatedier
c2c9f68a00 frps: improve login response message 2016-12-27 01:45:22 +08:00
fatedier
37470c26f0 frps: fix sometimes no response when frpc login to frps, see #142 2016-12-27 01:19:19 +08:00
fatedier
04a4591caa vhost: check host and location for url router 2016-12-27 01:01:39 +08:00
fatedier
8bf61d5e39 doc: add qq group 2016-12-26 12:18:46 +08:00
fatedier
659f84bab2 conf: update 2016-12-26 09:58:58 +08:00
fatedier
9faf4acd62 connection pool: ssh can't work when pool_count is set, fix #193 2016-12-26 01:55:54 +08:00
fatedier
4c3fb22295 modify version to 0.9.1 2016-12-25 14:26:16 +08:00
fatedier
d243f70125 doc: update contributors 2016-12-25 14:25:12 +08:00
fatedier
a56f068f8c subdomain: fix a bug that subdomain is not correct for https, close #194 2016-12-25 14:16:54 +08:00
fatedier
6a6ccc5302 Merge pull request #192 from fatedier/pr_182
http proxy support router by url
2016-12-24 12:41:20 -06:00
fatedier
6f90c3400c update conf 2016-12-25 02:09:01 +08:00
fatedier
eb4f779384 update 2016-12-25 01:53:23 +08:00
fatedier
59a34b81e0 Merge pull request #182 from xuebing1110/dev
[dev] http router by host and url
2016-12-24 09:08:20 -06:00
fatedier
b1d1a7a20a Merge branch 'pr_182' into dev 2016-12-24 08:51:26 -06:00
fatedier
6b34ed4644 update doc 2016-12-21 21:22:10 +08:00
XueBing
a44be1e2ed set default location empty string array
modified:   src/models/server/config.go
	modified:   src/utils/vhost/router.go

	modified:   src/models/server/config.go
	modified:   src/utils/vhost/router.go
2016-12-19 10:38:41 +08:00
XueBing
a4c05e6ff9 show "Port" incorrectly in dashboard 2016-12-18 23:43:08 +08:00
XueBing
d93dd82ed9 modify the getListener function && add TPS in dashboard 2016-12-18 23:35:14 +08:00
XueBing
edf4bc431d support the configure of url in "http" section 2016-12-18 22:40:58 +08:00
21 changed files with 533 additions and 118 deletions

View File

@@ -3,15 +3,23 @@ export GO15VENDOREXPERIMENT := 1
all: build all: build
build: gox app more build: app
gox:
go get github.com/mitchellh/gox
app: app:
gox -osarch "darwin/386 darwin/amd64 linux/386 linux/amd64 linux/arm windows/386 windows/amd64" ./src/... env GOOS=darwin GOARCH=386 go build -o ./frpc_darwin_386 ./src/cmd/frpc
env GOOS=darwin GOARCH=386 go build -o ./frps_darwin_386 ./src/cmd/frps
more: env GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./src/cmd/frpc
env GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./src/cmd/frps
env GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./src/cmd/frpc
env GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./src/cmd/frps
env GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./src/cmd/frpc
env GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./src/cmd/frps
env GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./src/cmd/frpc
env GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./src/cmd/frps
env GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./src/cmd/frpc
env GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./src/cmd/frps
env GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./src/cmd/frpc
env GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./src/cmd/frps
env GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc env GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc
env GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps env GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps
env GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc env GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc

View File

@@ -6,9 +6,9 @@
## What is frp? ## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, http and https protocol when requests can be forwarded by domains to backward web services. frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. Now, it supports tcp, udp, http and https protocol when requests can be forwarded by domains to backward web services.
## Catalog ## Table of Contents
<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc GFM -->
* [What can I do with frp?](#what-can-i-do-with-frp) * [What can I do with frp?](#what-can-i-do-with-frp)
@@ -29,6 +29,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Rewriting the Host Header](#rewriting-the-host-header) * [Rewriting the Host Header](#rewriting-the-host-header)
* [Password protecting your web service](#password-protecting-your-web-service) * [Password protecting your web service](#password-protecting-your-web-service)
* [Custom subdomain names](#custom-subdomain-names) * [Custom subdomain names](#custom-subdomain-names)
* [URL routing](#url-routing)
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy) * [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
* [Development Plan](#development-plan) * [Development Plan](#development-plan)
* [Contributing](#contributing) * [Contributing](#contributing)
@@ -108,7 +109,7 @@ Put **frpc** and **frpc.ini** to your server in LAN.
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip. Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
Howerver, we can expose a http or https service using frp. However, we can expose a http or https service using frp.
1. Modify frps.ini, configure a http reverse proxy named [web] and set http port as 8080, custom domain as `www.yourdomain.com`: 1. Modify frps.ini, configure a http reverse proxy named [web] and set http port as 8080, custom domain as `www.yourdomain.com`:
@@ -218,7 +219,7 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
Client that want's to register must set a global `auth_token` equals to frps.ini. Client that want's to register must set a global `auth_token` equals to frps.ini.
Note that time duration bewtween frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication. Note that time duration between frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout. Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
@@ -238,7 +239,7 @@ use_gzip = true
### Reload configures without frps stopped ### Reload configures without frps stopped
If your want to add a new reverse proxy and avoid restarting frps, you can use this function: If you want to add a new reverse proxy and avoid restarting frps, you can use this function:
1. `dashboard_port` should be set in frps.ini: 1. `dashboard_port` should be set in frps.ini:
@@ -414,6 +415,30 @@ Now you can visit your web service by host `test.frps.com`.
Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`. Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
### URL routing
frp support forward http requests to different backward web services by url routing.
`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
```ini
# frpc.ini
[web01]
privilege_mode = true
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
privilege_mode = true
type = http
local_port = 81
custom_domains = web.yourdomain.com
locations = /news,/about
```
Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
### Connect frps by HTTP PROXY ### Connect frps by HTTP PROXY
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file. frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
@@ -427,7 +452,6 @@ http_proxy = http://user:pwd@192.168.1.128:8080
## Development Plan ## Development Plan
* Url router.
* Log http request information in frps. * Log http request information in frps.
* Direct reverse proxy, like haproxy. * Direct reverse proxy, like haproxy.
* Load balance to different service in frpc. * Load balance to different service in frpc.
@@ -452,6 +476,8 @@ Interested in getting involved? We would like to help you!
If frp help you a lot, you can support us by: If frp help you a lot, you can support us by:
frp QQ group: 606194980
### AliPay ### AliPay
![donation-alipay](/doc/pic/donate-alipay.png) ![donation-alipay](/doc/pic/donate-alipay.png)
@@ -469,3 +495,5 @@ Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedie
* [Eric Larssen](https://github.com/ericlarssen) * [Eric Larssen](https://github.com/ericlarssen)
* [Damon Zhao](https://github.com/se77en) * [Damon Zhao](https://github.com/se77en)
* [Manfred Touron](https://github.com/moul) * [Manfred Touron](https://github.com/moul)
* [xuebing1110](https://github.com/xuebing1110)
* [Anbitioner](https://github.com/bingtianbaihua)

View File

@@ -4,7 +4,7 @@
[README](README.md) | [中文文档](README_zh.md) [README](README.md) | [中文文档](README_zh.md)
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。 frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, udp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。
## 目录 ## 目录
@@ -27,6 +27,7 @@ frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内
* [修改 Host Header](#修改-host-header) * [修改 Host Header](#修改-host-header)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务) * [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名) * [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由)
* [通过 HTTP PROXY 连接 frps](#通过-http-proxy-连接-frps) * [通过 HTTP PROXY 连接 frps](#通过-http-proxy-连接-frps)
* [开发计划](#开发计划) * [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献) * [为 frp 做贡献](#为-frp-做贡献)
@@ -426,6 +427,31 @@ frps 和 fprc 都启动成功后,通过 `test.frps.com` 就可以访问到内
同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。 同一个 http 或 https 类型的代理中 `custom_domains` 和 `subdomain` 可以同时配置。
### URL 路由
frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。
```ini
# frpc.ini
[web01]
privilege_mode = true
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
privilege_mode = true
type = http
local_port = 81
custom_domains = web.yourdomain.com
locations = /news,/about
```
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02其余的请求会被转发到 web01。
### 通过 HTTP PROXY 连接 frps ### 通过 HTTP PROXY 连接 frps
在只能通过代理访问外网的环境内frpc 支持通过 HTTP PROXY 和 frps 进行通信。 在只能通过代理访问外网的环境内frpc 支持通过 HTTP PROXY 和 frps 进行通信。
@@ -443,7 +469,6 @@ http_proxy = http://user:pwd@192.168.1.128:8080
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。 计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
* 支持 url 路由转发。
* frps 记录 http 请求日志。 * frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。 * frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。 * frpc 支持负载均衡到后端不同服务。
@@ -470,6 +495,8 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
frp 交流群606194980 (QQ 群号)
### 支付宝扫码捐赠 ### 支付宝扫码捐赠
![donate-alipay](/doc/pic/donate-alipay.png) ![donate-alipay](/doc/pic/donate-alipay.png)
@@ -487,3 +514,5 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
* [Eric Larssen](https://github.com/ericlarssen) * [Eric Larssen](https://github.com/ericlarssen)
* [Damon Zhao](https://github.com/se77en) * [Damon Zhao](https://github.com/se77en)
* [Manfred Touron](https://github.com/moul) * [Manfred Touron](https://github.com/moul)
* [xuebing1110](https://github.com/xuebing1110)
* [Anbitioner](https://github.com/bingtianbaihua)

View File

@@ -22,6 +22,10 @@ auth_token = 123
# for privilege mode # for privilege mode
privilege_token = 12345678 privilege_token = 12345678
# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 30
# heartbeat_interval = 10
# heartbeat_timeout = 30
# ssh is the proxy name same as server's configuration # ssh is the proxy name same as server's configuration
[ssh] [ssh]
@@ -30,11 +34,9 @@ type = tcp
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 22 local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false # true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = true use_encryption = false
# default is false # default is false
use_gzip = false use_gzip = false
# connections will be established in advance, default value is zero
pool_count = 10
[dns] [dns]
type = udp type = udp
@@ -47,6 +49,7 @@ type = http
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 80 local_port = 80
use_gzip = true use_gzip = true
# connections will be established in advance, default value is zero
pool_count = 20 pool_count = 20
# http username and password are safety certification for http protocol # http username and password are safety certification for http protocol
# if not set, you can access this custom_domains without certification # if not set, you can access this custom_domains without certification
@@ -77,5 +80,7 @@ local_ip = 127.0.0.1
local_port = 80 local_port = 80
use_gzip = true use_gzip = true
custom_domains = web03.yourdomain.com custom_domains = web03.yourdomain.com
# locations is only useful for http type
locations = /,/pic
host_header_rewrite = example.com host_header_rewrite = example.com
subdomain = dev subdomain = dev

View File

@@ -30,6 +30,10 @@ log_max_days = 3
privilege_mode = true privilege_mode = true
privilege_token = 12345678 privilege_token = 12345678
# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_timeout is 30
# heartbeat_timeout = 30
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit # only allow frpc to bind ports you list, if you set nothing, there won't be any limit
privilege_allow_ports = 2000-3000,3001,3003,4000-50000 privilege_allow_ports = 2000-3000,3001,3003,4000-50000

View File

@@ -25,6 +25,7 @@
<th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right">&#xe66d;</i></th> <th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right">&#xe66d;</i></th> <th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right">&#xe66d;</i></th> <th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].total_accept_conns';desc=!desc">TotalAcceptConns<i class="iconfont pull-right">&#xe66d;</i></th>
</tr> </tr>
</thead> </thead>
<tbody id="tab_body"> <tbody id="tab_body">
@@ -38,6 +39,7 @@
<td><span ng-bind="x.current_conns"></span></td> <td><span ng-bind="x.current_conns"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].flow_out"></span></td> <td><span ng-bind="x.daily[x.daily.length-1].flow_out"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].flow_in"></span></td> <td><span ng-bind="x.daily[x.daily.length-1].flow_in"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].total_accept_conns"></span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -63,7 +65,8 @@
listen_port: "<<< .ListenPort >>>", listen_port: "<<< .ListenPort >>>",
current_conns: <<< .CurrentConns >>> , current_conns: <<< .CurrentConns >>> ,
domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ], domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ],
stat: "<<< .Status >>>", locations: [ <<< range.Locations >>> "<<< . >>>", <<< end >>> ],
stat: "<<< .Status>>>",
use_encryption: "<<< .UseEncryption >>>", use_encryption: "<<< .UseEncryption >>>",
use_gzip: "<<< .UseGzip >>>", use_gzip: "<<< .UseGzip >>>",
privilege_mode: "<<< .PrivilegeMode >>>", privilege_mode: "<<< .PrivilegeMode >>>",
@@ -222,6 +225,10 @@
newrow += "<tr class='info_detail'><td colspan='4'>Domains</td><td colspan='4'>" + newrow += "<tr class='info_detail'><td colspan='4'>Domains</td><td colspan='4'>" +
alldata[index].domains[domainindex] + "</td><tr>"; alldata[index].domains[domainindex] + "</td><tr>";
} }
for (var locindex in alldata[index].locations) {
newrow += "<tr class='info_detail'><td colspan='4'>Locations</td><td colspan='4'>" +
alldata[index].locations[locindex] + "</td><tr>";
}
newrow += "<tr class='info_detail'><td colspan='4'>Ip</td><td colspan='4'>" + alldata[index].bind_addr + "</td><tr>"; newrow += "<tr class='info_detail'><td colspan='4'>Ip</td><td colspan='4'>" + alldata[index].bind_addr + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Status</td><td colspan='4'>" + alldata[index].stat + "</td><tr>"; newrow += "<tr class='info_detail'><td colspan='4'>Status</td><td colspan='4'>" + alldata[index].stat + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Encryption</td><td colspan='4'>" + alldata[index].use_encryption + "</td><tr>"; newrow += "<tr class='info_detail'><td colspan='4'>Encryption</td><td colspan='4'>" + alldata[index].use_encryption + "</td><tr>";

File diff suppressed because one or more lines are too long

View File

@@ -55,15 +55,24 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
var heartbeatTimeout bool = false var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() { timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true heartbeatTimeout = true
c.Close() if c != nil {
c.Close()
}
if cli != nil {
// if it's not udp type, nothing will happen
cli.CloseUdpTunnel()
cli.SetCloseFlag(true)
}
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name) log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
}) })
defer timer.Stop() defer timer.Stop()
for { for {
buf, err := c.ReadLine() buf, err := c.ReadLine()
if err == io.EOF || c == nil || c.IsClosed() { if err == io.EOF || c.IsClosed() {
timer.Stop()
c.Close() c.Close()
cli.SetCloseFlag(true)
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name) log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var delayTime time.Duration = 1 var delayTime time.Duration = 1
@@ -76,11 +85,14 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
msgSendChan = make(chan interface{}, 1024) msgSendChan = make(chan interface{}, 1024)
go heartbeatSender(c, msgSendChan) go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan) go msgSender(cli, c, msgSendChan)
cli.SetCloseFlag(false)
break break
} }
if delayTime < 60 { if delayTime < 30 {
delayTime = delayTime * 2 delayTime = delayTime * 2
} else {
delayTime = 30
} }
time.Sleep(delayTime * time.Second) time.Sleep(delayTime * time.Second)
} }
@@ -159,6 +171,7 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime)) privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime))
req.RemotePort = cli.RemotePort req.RemotePort = cli.RemotePort
req.CustomDomains = cli.CustomDomains req.CustomDomains = cli.CustomDomains
req.Locations = cli.Locations
req.PrivilegeKey = privilegeKey req.PrivilegeKey = privilegeKey
} else { } else {
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime)) authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))

View File

@@ -70,7 +70,7 @@ func controlWorker(c *conn.Conn) {
} }
// login when type is NewCtlConn or NewWorkConn // login when type is NewCtlConn or NewWorkConn
ret, info := doLogin(cliReq, c) ret, info, s := doLogin(cliReq, c)
// if login type is NewWorkConn, nothing will be send to frpc // if login type is NewWorkConn, nothing will be send to frpc
if cliReq.Type == consts.NewCtlConn { if cliReq.Type == consts.NewCtlConn {
cliRes := &msg.ControlRes{ cliRes := &msg.ControlRes{
@@ -85,7 +85,9 @@ func controlWorker(c *conn.Conn) {
return return
} }
} else { } else {
closeFlag = false if ret == 0 {
closeFlag = false
}
return return
} }
@@ -94,12 +96,6 @@ func controlWorker(c *conn.Conn) {
return return
} }
s, ok := server.GetProxyServer(cliReq.ProxyName)
if !ok {
log.Warn("ProxyName [%s] does not exist now", cliReq.ProxyName)
return
}
// create a channel for sending messages // create a channel for sending messages
msgSendChan := make(chan interface{}, 1024) msgSendChan := make(chan interface{}, 1024)
go msgSender(s, c, msgSendChan) go msgSender(s, c, msgSendChan)
@@ -199,7 +195,7 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
// NewCtlConn // NewCtlConn
// NewWorkConn // NewWorkConn
// NewWorkConnUdp // NewWorkConnUdp
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string, s *server.ProxyServer) {
ret = 1 ret = 1
// check if PrivilegeMode is enabled // check if PrivilegeMode is enabled
if req.PrivilegeMode && !server.PrivilegeMode { if req.PrivilegeMode && !server.PrivilegeMode {
@@ -208,10 +204,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
return return
} }
var ( var ok bool
s *server.ProxyServer
ok bool
)
s, ok = server.GetProxyServer(req.ProxyName) s, ok = server.GetProxyServer(req.ProxyName)
if req.PrivilegeMode && req.Type == consts.NewCtlConn { if req.PrivilegeMode && req.Type == consts.NewCtlConn {
log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName) log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
@@ -297,6 +290,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
} }
// set infomations from frpc // set infomations from frpc
s.BindAddr = server.BindAddr
s.UseEncryption = req.UseEncryption s.UseEncryption = req.UseEncryption
s.UseGzip = req.UseGzip s.UseGzip = req.UseGzip
s.HostHeaderRewrite = req.HostHeaderRewrite s.HostHeaderRewrite = req.HostHeaderRewrite
@@ -332,7 +326,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
} }
// update metric's proxy status // update metric's proxy status
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.ListenPort) metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
// start proxy and listen for user connections, no block // start proxy and listen for user connections, no block
err := s.Start(c) err := s.Start(c)

View File

@@ -35,9 +35,13 @@ type ProxyClient struct {
RemotePort int64 RemotePort int64
CustomDomains []string CustomDomains []string
Locations []string
udpTunnel *conn.Conn udpTunnel *conn.Conn
once sync.Once once sync.Once
closeFlag bool
mutex sync.RWMutex
} }
// if proxy type is udp, keep a tcp connection for transferring udp packages // if proxy type is udp, keep a tcp connection for transferring udp packages
@@ -47,7 +51,7 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
var c *conn.Conn var c *conn.Conn
udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort) udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort)
for { for {
if pc.udpTunnel == nil || pc.udpTunnel.IsClosed() { if !pc.IsClosed() && (pc.udpTunnel == nil || pc.udpTunnel.IsClosed()) {
if HttpProxy == "" { if HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port)) c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
} else { } else {
@@ -58,7 +62,7 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
continue continue
} }
log.Info("ProxyName [%s], udp tunnel reconnect to server [%s:%d] success", pc.Name, addr, port) log.Info("ProxyName [%s], udp tunnel connect to server [%s:%d] success", pc.Name, addr, port)
nowTime := time.Now().Unix() nowTime := time.Now().Unix()
req := &msg.ControlReq{ req := &msg.ControlReq{
@@ -81,8 +85,11 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
continue continue
} }
pc.mutex.Lock()
pc.udpTunnel = c pc.udpTunnel = c
udpProcessor.UpdateTcpConn(pc.udpTunnel) udpProcessor.UpdateTcpConn(pc.udpTunnel)
pc.mutex.Unlock()
udpProcessor.Run() udpProcessor.Run()
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -90,6 +97,14 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
}) })
} }
func (pc *ProxyClient) CloseUdpTunnel() {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
if pc.udpTunnel != nil {
pc.udpTunnel.Close()
}
}
func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort)) c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort))
if err != nil { if err != nil {
@@ -157,3 +172,15 @@ func (pc *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err err
return nil return nil
} }
func (pc *ProxyClient) SetCloseFlag(closeFlag bool) {
pc.mutex.Lock()
defer pc.mutex.Unlock()
pc.closeFlag = closeFlag
}
func (pc *ProxyClient) IsClosed() bool {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
return pc.closeFlag
}

View File

@@ -33,8 +33,8 @@ var (
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3 LogMaxDays int64 = 3
PrivilegeToken string = "" PrivilegeToken string = ""
HeartBeatInterval int64 = 20 HeartBeatInterval int64 = 10
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 30
) )
var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient) var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient)
@@ -98,6 +98,34 @@ func LoadConf(confFile string) (err error) {
authToken = tmpStr authToken = tmpStr
} }
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
} else {
HeartBeatInterval = v
}
}
if HeartBeatInterval <= 0 {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
}
if HeartBeatTimeout < HeartBeatInterval {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
}
// proxies // proxies
for name, section := range conf { for name, section := range conf {
if name != "common" { if name != "common" {
@@ -166,6 +194,9 @@ func LoadConf(confFile string) (err error) {
if ok { if ok {
proxyClient.HttpPassWord = tmpStr proxyClient.HttpPassWord = tmpStr
} }
}
if proxyClient.Type == "http" || proxyClient.Type == "https" {
// subdomain // subdomain
tmpStr, ok = section["subdomain"] tmpStr, ok = section["subdomain"]
if ok { if ok {
@@ -227,6 +258,14 @@ func LoadConf(confFile string) (err error) {
if !ok && proxyClient.SubDomain == "" { if !ok && proxyClient.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name) return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name)
} }
// locations
locations, ok := section["locations"]
if ok {
proxyClient.Locations = strings.Split(locations, ",")
} else {
proxyClient.Locations = []string{""}
}
} else if proxyClient.Type == "https" { } else if proxyClient.Type == "https" {
// custom_domains // custom_domains
domainStr, ok := section["custom_domains"] domainStr, ok := section["custom_domains"]

View File

@@ -34,6 +34,7 @@ type ServerMetric struct {
BindAddr string `json:"bind_addr"` BindAddr string `json:"bind_addr"`
ListenPort int64 `json:"listen_port"` ListenPort int64 `json:"listen_port"`
CustomDomains []string `json:"custom_domains"` CustomDomains []string `json:"custom_domains"`
Locations []string `json:"locations"`
Status string `json:"status"` Status string `json:"status"`
UseEncryption bool `json:"use_encryption"` UseEncryption bool `json:"use_encryption"`
UseGzip bool `json:"use_gzip"` UseGzip bool `json:"use_gzip"`
@@ -112,7 +113,7 @@ func GetProxyMetrics(proxyName string) *ServerMetric {
func SetProxyInfo(proxyName string, proxyType, bindAddr string, func SetProxyInfo(proxyName string, proxyType, bindAddr string,
useEncryption, useGzip, privilegeMode bool, customDomains []string, useEncryption, useGzip, privilegeMode bool, customDomains []string,
listenPort int64) { locations []string, listenPort int64) {
smMutex.Lock() smMutex.Lock()
info, ok := ServerMetricInfoMap[proxyName] info, ok := ServerMetricInfoMap[proxyName]
if !ok { if !ok {
@@ -127,6 +128,7 @@ func SetProxyInfo(proxyName string, proxyType, bindAddr string,
info.BindAddr = bindAddr info.BindAddr = bindAddr
info.ListenPort = listenPort info.ListenPort = listenPort
info.CustomDomains = customDomains info.CustomDomains = customDomains
info.Locations = locations
ServerMetricInfoMap[proxyName] = info ServerMetricInfoMap[proxyName] = info
smMutex.Unlock() smMutex.Unlock()
} }

View File

@@ -34,6 +34,7 @@ type ControlReq struct {
ProxyType string `json:"proxy_type"` ProxyType string `json:"proxy_type"`
RemotePort int64 `json:"remote_port"` RemotePort int64 `json:"remote_port"`
CustomDomains []string `json:"custom_domains, omitempty"` CustomDomains []string `json:"custom_domains, omitempty"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"` HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUserName string `json:"http_username"` HttpUserName string `json:"http_username"`
HttpPassWord string `json:"http_password"` HttpPassWord string `json:"http_password"`

View File

@@ -51,7 +51,7 @@ var (
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected // if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts map[int64]struct{} PrivilegeAllowPorts map[int64]struct{}
MaxPoolCount int64 = 100 MaxPoolCount int64 = 100
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 30
UserConnTimeout int64 = 10 UserConnTimeout int64 = 10
VhostHttpMuxer *vhost.HttpMuxer VhostHttpMuxer *vhost.HttpMuxer
@@ -237,6 +237,16 @@ func loadCommonConf(confFile string) error {
if ok { if ok {
SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost)) SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost))
} }
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
return nil return nil
} }
@@ -304,6 +314,14 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
} else { } else {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type is http", proxyServer.Name) return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type is http", proxyServer.Name)
} }
// locations
locations, ok := section["locations"]
if ok {
proxyServer.Locations = strings.Split(locations, ",")
} else {
proxyServer.Locations = []string{""}
}
} else if proxyServer.Type == "https" { } else if proxyServer.Type == "https" {
// for https // for https
proxyServer.ListenPort = VhostHttpsPort proxyServer.ListenPort = VhostHttpsPort
@@ -332,7 +350,7 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
// set metric statistics of all proxies // set metric statistics of all proxies
for name, p := range proxyServers { for name, p := range proxyServers {
metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip, metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip,
p.PrivilegeMode, p.CustomDomains, p.ListenPort) p.PrivilegeMode, p.CustomDomains, p.Locations, p.ListenPort)
} }
return proxyServers, nil return proxyServers, nil
} }
@@ -387,15 +405,16 @@ func CreateProxy(s *ProxyServer) error {
if oldServer.Status == consts.Working { if oldServer.Status == consts.Working {
return fmt.Errorf("this proxy is already working now") return fmt.Errorf("this proxy is already working now")
} }
oldServer.Close() oldServer.Lock()
oldServer.Release()
oldServer.Unlock()
if oldServer.PrivilegeMode { if oldServer.PrivilegeMode {
delete(ProxyServers, s.Name) delete(ProxyServers, s.Name)
} }
} }
ProxyServers[s.Name] = s ProxyServers[s.Name] = s
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip,
s.PrivilegeMode, s.CustomDomains, s.ListenPort) s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
s.Init()
return nil return nil
} }

View File

@@ -39,6 +39,7 @@ type ProxyServer struct {
BindAddr string BindAddr string
ListenPort int64 ListenPort int64
CustomDomains []string CustomDomains []string
Locations []string
Status int64 Status int64
CtlConn *conn.Conn // control connection with frpc CtlConn *conn.Conn // control connection with frpc
@@ -56,6 +57,7 @@ type ProxyServer struct {
func NewProxyServer() (p *ProxyServer) { func NewProxyServer() (p *ProxyServer) {
p = &ProxyServer{ p = &ProxyServer{
CustomDomains: make([]string, 0), CustomDomains: make([]string, 0),
Locations: make([]string, 0),
} }
return p return p
} }
@@ -77,9 +79,12 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
p.ListenPort = VhostHttpsPort p.ListenPort = VhostHttpsPort
} }
p.CustomDomains = req.CustomDomains p.CustomDomains = req.CustomDomains
p.Locations = req.Locations
p.HostHeaderRewrite = req.HostHeaderRewrite p.HostHeaderRewrite = req.HostHeaderRewrite
p.HttpUserName = req.HttpUserName p.HttpUserName = req.HttpUserName
p.HttpPassWord = req.HttpPassWord p.HttpPassWord = req.HttpPassWord
p.Init()
return return
} }
@@ -108,6 +113,15 @@ func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
return false return false
} }
} }
if len(p.Locations) != len(p2.Locations) {
return false
}
for i, _ := range p.Locations {
if p.Locations[i] != p2.Locations[i] {
return false
}
}
return true return true
} }
@@ -131,26 +145,58 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
p.listeners = append(p.listeners, l) p.listeners = append(p.listeners, l)
} else if p.Type == "http" { } else if p.Type == "http" {
for _, domain := range p.CustomDomains { for _, domain := range p.CustomDomains {
l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(domain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, location)
p.listeners = append(p.listeners, l)
}
}
}
if p.SubDomain != "" {
if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(p.SubDomain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, location)
p.listeners = append(p.listeners, l)
}
}
}
} else if p.Type == "https" {
for _, domain := range p.CustomDomains {
l, err := VhostHttpsMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil { if err != nil {
return err return err
} }
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, domain)
p.listeners = append(p.listeners, l) p.listeners = append(p.listeners, l)
} }
if p.SubDomain != "" { if p.SubDomain != "" {
l, err := VhostHttpMuxer.Listen(p.SubDomain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord) l, err := VhostHttpsMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
}
} else if p.Type == "https" {
for _, domain := range p.CustomDomains {
l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil { if err != nil {
return err return err
} }
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, p.SubDomain)
p.listeners = append(p.listeners, l) p.listeners = append(p.listeners, l)
} }
} }
@@ -233,6 +279,20 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
func (p *ProxyServer) Close() { func (p *ProxyServer) Close() {
p.Lock() p.Lock()
defer p.Unlock()
oldStatus := p.Status
p.Release()
// if the proxy created by PrivilegeMode, delete it when closed
if p.PrivilegeMode && oldStatus != consts.Closed {
// NOTE: this will take the global ProxyServerMap's lock
// if we only want to release resources, use Release() instead
DeleteProxy(p.Name)
}
}
func (p *ProxyServer) Release() {
if p.Status != consts.Closed { if p.Status != consts.Closed {
p.Status = consts.Closed p.Status = consts.Closed
for _, l := range p.listeners { for _, l := range p.listeners {
@@ -240,10 +300,22 @@ func (p *ProxyServer) Close() {
l.Close() l.Close()
} }
} }
close(p.ctlMsgChan) if p.ctlMsgChan != nil {
close(p.workConnChan) close(p.ctlMsgChan)
close(p.udpSenderChan) p.ctlMsgChan = nil
close(p.closeChan) }
if p.workConnChan != nil {
close(p.workConnChan)
p.workConnChan = nil
}
if p.udpSenderChan != nil {
close(p.udpSenderChan)
p.udpSenderChan = nil
}
if p.closeChan != nil {
close(p.closeChan)
p.closeChan = nil
}
if p.CtlConn != nil { if p.CtlConn != nil {
p.CtlConn.Close() p.CtlConn.Close()
} }
@@ -256,11 +328,6 @@ func (p *ProxyServer) Close() {
} }
} }
metric.SetStatus(p.Name, p.Status) metric.SetStatus(p.Name, p.Status)
// if the proxy created by PrivilegeMode, delete it when closed
if p.PrivilegeMode {
DeleteProxy(p.Name)
}
p.Unlock()
} }
func (p *ProxyServer) WaitUserConn() (closeFlag bool) { func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
@@ -346,6 +413,7 @@ func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) {
err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name) err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
return return
} }
log.Debug("ProxyName [%s], get work connection from pool", p.Name)
default: default:
// no work connections available in the poll, send message to frpc to get more // no work connections available in the poll, send message to frpc to get more
p.ctlMsgChan <- 1 p.ctlMsgChan <- 1

View File

@@ -16,6 +16,7 @@ package conn
import ( import (
"bufio" "bufio"
"bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
@@ -25,6 +26,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/src/utils/pool"
) )
type Listener struct { type Listener struct {
@@ -61,11 +64,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
continue continue
} }
c := &Conn{ c := NewConn(conn)
TcpConn: conn,
closeFlag: false,
}
c.Reader = bufio.NewReader(c.TcpConn)
l.accept <- c l.accept <- c
} }
}() }()
@@ -95,20 +94,23 @@ func (l *Listener) Close() error {
type Conn struct { type Conn struct {
TcpConn net.Conn TcpConn net.Conn
Reader *bufio.Reader Reader *bufio.Reader
buffer *bytes.Buffer
closeFlag bool closeFlag bool
mutex sync.RWMutex
mutex sync.RWMutex
} }
func NewConn(conn net.Conn) (c *Conn) { func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{} c = &Conn{
c.TcpConn = conn TcpConn: conn,
buffer: nil,
closeFlag: false,
}
c.Reader = bufio.NewReader(c.TcpConn) c.Reader = bufio.NewReader(c.TcpConn)
c.closeFlag = false return
return c
} }
func ConnectServer(addr string) (c *Conn, err error) { func ConnectServer(addr string) (c *Conn, err error) {
c = &Conn{}
servertAddr, err := net.ResolveTCPAddr("tcp", addr) servertAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil { if err != nil {
return return
@@ -117,9 +119,7 @@ func ConnectServer(addr string) (c *Conn, err error) {
if err != nil { if err != nil {
return return
} }
c.TcpConn = conn c = NewConn(conn)
c.Reader = bufio.NewReader(c.TcpConn)
c.closeFlag = false
return c, nil return c, nil
} }
@@ -185,7 +185,23 @@ func (c *Conn) GetLocalAddr() (addr string) {
} }
func (c *Conn) Read(p []byte) (n int, err error) { func (c *Conn) Read(p []byte) (n int, err error) {
n, err = c.Reader.Read(p) c.mutex.RLock()
if c.buffer == nil {
c.mutex.RUnlock()
return c.Reader.Read(p)
}
c.mutex.RUnlock()
n, err = c.buffer.Read(p)
if err == io.EOF {
c.mutex.Lock()
c.buffer = nil
c.mutex.Unlock()
var n2 int
n2, err = c.Reader.Read(p[n:])
n += n2
}
return return
} }
@@ -212,6 +228,16 @@ func (c *Conn) WriteString(content string) (err error) {
return err return err
} }
func (c *Conn) AppendReaderBuffer(content []byte) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.buffer == nil {
c.buffer = bytes.NewBuffer(make([]byte, 0, 2048))
}
c.buffer.Write(content)
}
func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) SetDeadline(t time.Time) error {
return c.TcpConn.SetDeadline(t) return c.TcpConn.SetDeadline(t)
} }
@@ -238,22 +264,36 @@ func (c *Conn) IsClosed() (closeFlag bool) {
} }
// when you call this function, you should make sure that // when you call this function, you should make sure that
// remote client won't send any bytes to this socket // no bytes were read before
func (c *Conn) CheckClosed() bool { func (c *Conn) CheckClosed() bool {
c.mutex.RLock() c.mutex.RLock()
if c.closeFlag { if c.closeFlag {
c.mutex.RUnlock()
return true return true
} }
c.mutex.RUnlock() c.mutex.RUnlock()
tmp := pool.GetBuf(2048)
defer pool.PutBuf(tmp)
err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond)) err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil { if err != nil {
c.Close() c.Close()
return true return true
} }
var tmp []byte = make([]byte, 1) n, err := c.TcpConn.Read(tmp)
_, err = c.TcpConn.Read(tmp) if err == io.EOF {
return true
}
var tmp2 []byte = make([]byte, 1)
err = c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil {
c.Close()
return true
}
n2, err := c.TcpConn.Read(tmp2)
if err == io.EOF { if err == io.EOF {
return true return true
} }
@@ -263,5 +303,12 @@ func (c *Conn) CheckClosed() bool {
c.Close() c.Close()
return true return true
} }
if n > 0 {
c.AppendReaderBuffer(tmp[:n])
}
if n2 > 0 {
c.AppendReaderBuffer(tmp2[:n2])
}
return false return false
} }

View File

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

View File

@@ -45,6 +45,8 @@ func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err erro
// hostName // hostName
tmpArr := strings.Split(request.Host, ":") tmpArr := strings.Split(request.Host, ":")
reqInfoMap["Host"] = tmpArr[0] reqInfoMap["Host"] = tmpArr[0]
reqInfoMap["Path"] = request.URL.Path
reqInfoMap["Scheme"] = request.URL.Scheme
// Authorization // Authorization
authStr := request.Header.Get("Authorization") authStr := request.Header.Get("Authorization")

View File

@@ -186,5 +186,6 @@ func GetHttpsHostname(c *conn.Conn) (sc net.Conn, _ map[string]string, err error
return sc, reqInfoMap, err return sc, reqInfoMap, err
} }
reqInfoMap["Host"] = host reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "https"
return sc, reqInfoMap, nil return sc, reqInfoMap, nil
} }

114
src/utils/vhost/router.go Normal file
View File

@@ -0,0 +1,114 @@
package vhost
import (
"sort"
"strings"
"sync"
)
type VhostRouters struct {
RouterByDomain map[string][]*VhostRouter
mutex sync.RWMutex
}
type VhostRouter struct {
domain string
location string
listener *Listener
}
func NewVhostRouters() *VhostRouters {
return &VhostRouters{
RouterByDomain: make(map[string][]*VhostRouter),
}
}
func (r *VhostRouters) Add(domain, location string, l *Listener) {
r.mutex.Lock()
defer r.mutex.Unlock()
vrs, found := r.RouterByDomain[domain]
if !found {
vrs = make([]*VhostRouter, 0, 1)
}
vr := &VhostRouter{
domain: domain,
location: location,
listener: l,
}
vrs = append(vrs, vr)
sort.Sort(sort.Reverse(ByLocation(vrs)))
r.RouterByDomain[domain] = vrs
}
func (r *VhostRouters) Del(domain, location string) {
r.mutex.Lock()
defer r.mutex.Unlock()
vrs, found := r.RouterByDomain[domain]
if !found {
return
}
for i, vr := range vrs {
if vr.location == location {
if len(vrs) > i+1 {
r.RouterByDomain[domain] = append(vrs[:i], vrs[i+1:]...)
} else {
r.RouterByDomain[domain] = vrs[:i]
}
}
}
}
func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
vrs, found := r.RouterByDomain[host]
if !found {
return
}
// can't support load balance, will to do
for _, vr = range vrs {
if strings.HasPrefix(path, vr.location) {
return vr, true
}
}
return
}
func (r *VhostRouters) Exist(host, path string) (vr *VhostRouter, exist bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
vrs, found := r.RouterByDomain[host]
if !found {
return
}
for _, vr = range vrs {
if path == vr.location {
return vr, true
}
}
return
}
// sort by location
type ByLocation []*VhostRouter
func (a ByLocation) Len() int {
return len(a)
}
func (a ByLocation) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByLocation) Less(i, j int) bool {
return strings.Compare(a[i].location, a[j].location) < 0
}

View File

@@ -29,71 +29,75 @@ type httpAuthFunc func(*conn.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error) type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
type VhostMuxer struct { type VhostMuxer struct {
listener *conn.Listener listener *conn.Listener
timeout time.Duration timeout time.Duration
vhostFunc muxFunc vhostFunc muxFunc
authFunc httpAuthFunc authFunc httpAuthFunc
rewriteFunc hostRewriteFunc rewriteFunc hostRewriteFunc
registryMap map[string]*Listener registryRouter *VhostRouters
mutex sync.RWMutex mutex sync.RWMutex
} }
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) { func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{ mux = &VhostMuxer{
listener: listener, listener: listener,
timeout: timeout, timeout: timeout,
vhostFunc: vhostFunc, vhostFunc: vhostFunc,
authFunc: authFunc, authFunc: authFunc,
rewriteFunc: rewriteFunc, rewriteFunc: rewriteFunc,
registryMap: make(map[string]*Listener), registryRouter: NewVhostRouters(),
} }
go mux.run() go mux.run()
return mux, nil return mux, nil
} }
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil, then rewrite the host header to rewriteHost // listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil, then rewrite the host header to rewriteHost
func (v *VhostMuxer) Listen(name string, rewriteHost, userName, passWord string) (l *Listener, err error) { func (v *VhostMuxer) Listen(name, location, rewriteHost, userName, passWord string) (l *Listener, err error) {
v.mutex.Lock() v.mutex.Lock()
defer v.mutex.Unlock() defer v.mutex.Unlock()
if _, exist := v.registryMap[name]; exist {
return nil, fmt.Errorf("domain name %s is already bound", name) _, ok := v.registryRouter.Exist(name, location)
if ok {
return nil, fmt.Errorf("hostname [%s] location [%s] is already registered", name, location)
} }
l = &Listener{ l = &Listener{
name: name, name: name,
location: location,
rewriteHost: rewriteHost, rewriteHost: rewriteHost,
userName: userName, userName: userName,
passWord: passWord, passWord: passWord,
mux: v, mux: v,
accept: make(chan *conn.Conn), accept: make(chan *conn.Conn),
} }
v.registryMap[name] = l v.registryRouter.Add(name, location, l)
return l, nil return l, nil
} }
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) { func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
v.mutex.RLock() v.mutex.RLock()
defer v.mutex.RUnlock() 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
l, exist = v.registryMap[name] vr, found := v.registryRouter.Get(name, path)
if exist { if found {
return l, exist return vr.listener, true
} }
domainSplit := strings.Split(name, ".") domainSplit := strings.Split(name, ".")
if len(domainSplit) < 3 { if len(domainSplit) < 3 {
return l, false return l, false
} }
domainSplit[0] = "*" domainSplit[0] = "*"
name = strings.Join(domainSplit, ".") name = strings.Join(domainSplit, ".")
l, exist = v.registryMap[name]
return l, exist
}
func (v *VhostMuxer) unRegister(name string) { vr, found = v.registryRouter.Get(name, path)
v.mutex.Lock() if !found {
defer v.mutex.Unlock() return
delete(v.registryMap, name) }
return vr.listener, true
} }
func (v *VhostMuxer) run() { func (v *VhostMuxer) run() {
@@ -119,8 +123,8 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
} }
name := strings.ToLower(reqInfoMap["Host"]) name := strings.ToLower(reqInfoMap["Host"])
// get listener by hostname path := strings.ToLower(reqInfoMap["Path"])
l, ok := v.getListener(name) l, ok := v.getListener(name, path)
if !ok { if !ok {
c.Close() c.Close()
return return
@@ -150,6 +154,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
type Listener struct { type Listener struct {
name string name string
location string
rewriteHost string rewriteHost string
userName string userName string
passWord string passWord string
@@ -177,7 +182,7 @@ func (l *Listener) Accept() (*conn.Conn, error) {
} }
func (l *Listener) Close() error { func (l *Listener) Close() error {
l.mux.unRegister(l.name) l.mux.registryRouter.Del(l.name, l.location)
close(l.accept) close(l.accept)
return nil return nil
} }
@@ -207,16 +212,18 @@ func (sc *sharedConn) Read(p []byte) (n int, err error) {
sc.Unlock() sc.Unlock()
return sc.Conn.Read(p) return sc.Conn.Read(p)
} }
sc.Unlock()
n, err = sc.buff.Read(p) n, err = sc.buff.Read(p)
if err == io.EOF { if err == io.EOF {
sc.Lock()
sc.buff = nil sc.buff = nil
sc.Unlock()
var n2 int var n2 int
n2, err = sc.Conn.Read(p[n:]) n2, err = sc.Conn.Read(p[n:])
n += n2 n += n2
} }
sc.Unlock()
return return
} }