mirror of
https://github.com/fatedier/frp.git
synced 2024-11-15 04:24:55 +01:00
commit
456ce09061
3
Makefile
3
Makefile
@ -53,6 +53,3 @@ clean:
|
|||||||
rm -f ./bin/frpc
|
rm -f ./bin/frpc
|
||||||
rm -f ./bin/frps
|
rm -f ./bin/frps
|
||||||
cd ./tests && ./clean_test.sh && cd -
|
cd ./tests && ./clean_test.sh && cd -
|
||||||
|
|
||||||
save:
|
|
||||||
godep save ./...
|
|
||||||
|
48
README.md
48
README.md
@ -20,6 +20,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
|||||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
|
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||||
* [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)
|
||||||
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
||||||
@ -214,6 +215,32 @@ Configure frps same as above.
|
|||||||
|
|
||||||
`curl http://x.x.x.x:6000/version`
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
|
### Expose a simple http file server
|
||||||
|
|
||||||
|
A simple way to visit files in the LAN.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_static_file]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = /tmp/file
|
||||||
|
plugin_strip_prefix = static
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||||
|
|
||||||
### 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.
|
||||||
@ -576,11 +603,26 @@ server_port = 7000
|
|||||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Range ports mapping
|
||||||
|
|
||||||
|
Proxy name has prefix `range:` will support mapping range ports.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[range:test_tcp]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 6000-6006,6007
|
||||||
|
remote_port = 6000-6006,6007
|
||||||
|
```
|
||||||
|
|
||||||
|
frpc will generate 6 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_5`.
|
||||||
|
|
||||||
### Plugin
|
### Plugin
|
||||||
|
|
||||||
frpc only forward request to local tcp or udp port by default.
|
frpc only forward request to local tcp or udp port by default.
|
||||||
|
|
||||||
Plugin is used for providing rich features. There are built-in plugins such as **unix_domain_socket**, **http_proxy**, **socks5** and you can see [example usage](#example-usage).
|
Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
|
||||||
|
|
||||||
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||||
|
|
||||||
@ -598,17 +640,13 @@ plugin_http_passwd = abc
|
|||||||
|
|
||||||
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||||
|
|
||||||
|
|
||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* 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.
|
||||||
* Frpc can directly be a webserver for static files.
|
|
||||||
* P2p communicate by making udp hole to penetrate NAT.
|
|
||||||
* kubernetes ingress support.
|
* kubernetes ingress support.
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Interested in getting involved? We would like to help you!
|
Interested in getting involved? We would like to help you!
|
||||||
|
57
README_zh.md
57
README_zh.md
@ -18,6 +18,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||||
|
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
* [点对点内网穿透](#点对点内网穿透)
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||||
@ -39,6 +40,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [自定义二级域名](#自定义二级域名)
|
* [自定义二级域名](#自定义二级域名)
|
||||||
* [URL 路由](#url-路由)
|
* [URL 路由](#url-路由)
|
||||||
* [通过代理连接 frps](#通过代理连接-frps)
|
* [通过代理连接 frps](#通过代理连接-frps)
|
||||||
|
* [范围端口映射](#范围端口映射)
|
||||||
* [插件](#插件)
|
* [插件](#插件)
|
||||||
* [开发计划](#开发计划)
|
* [开发计划](#开发计划)
|
||||||
* [为 frp 做贡献](#为-frp-做贡献)
|
* [为 frp 做贡献](#为-frp-做贡献)
|
||||||
@ -192,11 +194,11 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
|||||||
|
|
||||||
### 转发 Unix域套接字
|
### 转发 Unix域套接字
|
||||||
|
|
||||||
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
|
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||||
|
|
||||||
frps 的部署步骤同上。
|
frps 的部署步骤同上。
|
||||||
|
|
||||||
1. 启动 frpc,启用 unix_domain_socket 插件,配置如下:
|
1. 启动 frpc,启用 `unix_domain_socket` 插件,配置如下:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@ -215,6 +217,34 @@ frps 的部署步骤同上。
|
|||||||
|
|
||||||
`curl http://x.x.x.x:6000/version`
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
|
### 对外提供简单的文件访问服务
|
||||||
|
|
||||||
|
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
|
||||||
|
|
||||||
|
frps 的部署步骤同上。
|
||||||
|
|
||||||
|
1. 启动 frpc,启用 `static_file` 插件,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_static_file]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = static_file
|
||||||
|
# 要对外暴露的文件目录
|
||||||
|
plugin_local_path = /tmp/file
|
||||||
|
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
|
||||||
|
plugin_strip_prefix = static
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||||
|
|
||||||
### 安全地暴露内网服务
|
### 安全地暴露内网服务
|
||||||
|
|
||||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||||
@ -609,11 +639,30 @@ server_port = 7000
|
|||||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 范围端口映射
|
||||||
|
|
||||||
|
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
|
||||||
|
|
||||||
|
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名。
|
||||||
|
|
||||||
|
例如要映射本地 6000-6005, 6007 这6个端口,主要配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[range:test_tcp]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 6000-6006,6007
|
||||||
|
remote_port = 6000-6006,6007
|
||||||
|
```
|
||||||
|
|
||||||
|
实际连接成功后会创建 6 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_5`。
|
||||||
|
|
||||||
### 插件
|
### 插件
|
||||||
|
|
||||||
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||||
|
|
||||||
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。
|
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
|
||||||
|
|
||||||
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
||||||
|
|
||||||
@ -638,8 +687,6 @@ plugin_http_passwd = abc
|
|||||||
* frps 记录 http 请求日志。
|
* frps 记录 http 请求日志。
|
||||||
* frps 支持直接反向代理,类似 haproxy。
|
* frps 支持直接反向代理,类似 haproxy。
|
||||||
* frpc 支持负载均衡到后端不同服务。
|
* frpc 支持负载均衡到后端不同服务。
|
||||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
|
||||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
|
||||||
* 集成对 k8s 等平台的支持。
|
* 集成对 k8s 等平台的支持。
|
||||||
|
|
||||||
## 为 frp 做贡献
|
## 为 frp 做贡献
|
||||||
|
@ -89,8 +89,8 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m
|
|||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
svr: svr,
|
svr: svr,
|
||||||
loginMsg: loginMsg,
|
loginMsg: loginMsg,
|
||||||
sendCh: make(chan msg.Message, 10),
|
sendCh: make(chan msg.Message, 100),
|
||||||
readCh: make(chan msg.Message, 10),
|
readCh: make(chan msg.Message, 100),
|
||||||
closedCh: make(chan int),
|
closedCh: make(chan int),
|
||||||
readerShutdown: shutdown.New(),
|
readerShutdown: shutdown.New(),
|
||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
@ -98,7 +98,7 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m
|
|||||||
Logger: log.NewPrefixLogger(""),
|
Logger: log.NewPrefixLogger(""),
|
||||||
}
|
}
|
||||||
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
|
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
|
||||||
ctl.pm.Reload(pxyCfgs, visitorCfgs)
|
ctl.pm.Reload(pxyCfgs, visitorCfgs, false)
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ func (ctl *Control) Run() (err error) {
|
|||||||
|
|
||||||
// start all local visitors and send NewProxy message for all configured proxies
|
// start all local visitors and send NewProxy message for all configured proxies
|
||||||
ctl.pm.Reset(ctl.sendCh, ctl.runId)
|
ctl.pm.Reset(ctl.sendCh, ctl.runId)
|
||||||
ctl.pm.CheckAndStartProxy()
|
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,20 +360,20 @@ func (ctl *Control) msgHandler() {
|
|||||||
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
|
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
|
||||||
func (ctl *Control) worker() {
|
func (ctl *Control) worker() {
|
||||||
go ctl.msgHandler()
|
go ctl.msgHandler()
|
||||||
go ctl.writer()
|
|
||||||
go ctl.reader()
|
go ctl.reader()
|
||||||
|
go ctl.writer()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
maxDelayTime := 20 * time.Second
|
maxDelayTime := 20 * time.Second
|
||||||
delayTime := time.Second
|
delayTime := time.Second
|
||||||
|
|
||||||
checkInterval := 10 * time.Second
|
checkInterval := 60 * time.Second
|
||||||
checkProxyTicker := time.NewTicker(checkInterval)
|
checkProxyTicker := time.NewTicker(checkInterval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-checkProxyTicker.C:
|
case <-checkProxyTicker.C:
|
||||||
// every 10 seconds, check which proxy registered failed and reregister it to server
|
// check which proxy registered failed and reregister it to server
|
||||||
ctl.pm.CheckAndStartProxy()
|
ctl.pm.CheckAndStartProxy([]string{ProxyStatusStartErr, ProxyStatusClosed})
|
||||||
case _, ok := <-ctl.closedCh:
|
case _, ok := <-ctl.closedCh:
|
||||||
// we won't get any variable from this channel
|
// we won't get any variable from this channel
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -413,8 +413,8 @@ func (ctl *Control) worker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init related channels and variables
|
// init related channels and variables
|
||||||
ctl.sendCh = make(chan msg.Message, 10)
|
ctl.sendCh = make(chan msg.Message, 100)
|
||||||
ctl.readCh = make(chan msg.Message, 10)
|
ctl.readCh = make(chan msg.Message, 100)
|
||||||
ctl.closedCh = make(chan int)
|
ctl.closedCh = make(chan int)
|
||||||
ctl.readerShutdown = shutdown.New()
|
ctl.readerShutdown = shutdown.New()
|
||||||
ctl.writerShutdown = shutdown.New()
|
ctl.writerShutdown = shutdown.New()
|
||||||
@ -427,7 +427,7 @@ func (ctl *Control) worker() {
|
|||||||
go ctl.reader()
|
go ctl.reader()
|
||||||
|
|
||||||
// start all configured proxies
|
// start all configured proxies
|
||||||
ctl.pm.CheckAndStartProxy()
|
ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew, ProxyStatusClosed})
|
||||||
|
|
||||||
checkProxyTicker.Stop()
|
checkProxyTicker.Stop()
|
||||||
checkProxyTicker = time.NewTicker(checkInterval)
|
checkProxyTicker = time.NewTicker(checkInterval)
|
||||||
@ -437,6 +437,6 @@ func (ctl *Control) worker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
||||||
err := ctl.pm.Reload(pxyCfgs, visitorCfgs)
|
err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProxyStatusNew = "new"
|
ProxyStatusNew = "new"
|
||||||
ProxyStatusStartErr = "start error"
|
ProxyStatusStartErr = "start error"
|
||||||
ProxyStatusRunning = "running"
|
ProxyStatusWaitStart = "wait start"
|
||||||
ProxyStatusClosed = "closed"
|
ProxyStatusRunning = "running"
|
||||||
|
ProxyStatusClosed = "closed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyManager struct {
|
type ProxyManager struct {
|
||||||
@ -69,14 +70,10 @@ func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) IsRunning() bool {
|
func (pw *ProxyWrapper) GetStatusStr() string {
|
||||||
pw.mu.RLock()
|
pw.mu.RLock()
|
||||||
defer pw.mu.RUnlock()
|
defer pw.mu.RUnlock()
|
||||||
if pw.Status == ProxyStatusRunning {
|
return pw.Status
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
||||||
@ -93,6 +90,12 @@ func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
|
|||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pw *ProxyWrapper) WaitStart() {
|
||||||
|
pw.mu.Lock()
|
||||||
|
defer pw.mu.Unlock()
|
||||||
|
pw.Status = ProxyStatusWaitStart
|
||||||
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
|
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
|
||||||
if pw.pxy != nil {
|
if pw.pxy != nil {
|
||||||
pw.pxy.Close()
|
pw.pxy.Close()
|
||||||
@ -210,7 +213,8 @@ func (pm *ProxyManager) CloseProxies() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) CheckAndStartProxy() {
|
// pxyStatus: check and start proxies in which status
|
||||||
|
func (pm *ProxyManager) CheckAndStartProxy(pxyStatus []string) {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
defer pm.mu.RUnlock()
|
defer pm.mu.RUnlock()
|
||||||
if pm.closed {
|
if pm.closed {
|
||||||
@ -219,13 +223,18 @@ func (pm *ProxyManager) CheckAndStartProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pxy := range pm.proxies {
|
for _, pxy := range pm.proxies {
|
||||||
if !pxy.IsRunning() {
|
status := pxy.GetStatusStr()
|
||||||
var newProxyMsg msg.NewProxy
|
for _, s := range pxyStatus {
|
||||||
pxy.Cfg.UnMarshalToMsg(&newProxyMsg)
|
if status == s {
|
||||||
err := pm.sendMsg(&newProxyMsg)
|
var newProxyMsg msg.NewProxy
|
||||||
if err != nil {
|
pxy.Cfg.UnMarshalToMsg(&newProxyMsg)
|
||||||
pm.Warn("[%s] proxy send NewProxy message error")
|
err := pm.sendMsg(&newProxyMsg)
|
||||||
return
|
if err != nil {
|
||||||
|
pm.Warn("[%s] proxy send NewProxy message error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.WaitStart()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,9 +254,14 @@ func (pm *ProxyManager) CheckAndStartProxy() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
|
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf, startNow bool) error {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer func() {
|
||||||
|
pm.mu.Unlock()
|
||||||
|
if startNow {
|
||||||
|
go pm.CheckAndStartProxy([]string{ProxyStatusNew})
|
||||||
|
}
|
||||||
|
}()
|
||||||
if pm.closed {
|
if pm.closed {
|
||||||
err := fmt.Errorf("Reload error: ProxyManager is closed now")
|
err := fmt.Errorf("Reload error: ProxyManager is closed now")
|
||||||
pm.Warn(err.Error())
|
pm.Warn(err.Error())
|
||||||
|
@ -99,7 +99,7 @@ func main() {
|
|||||||
if args["status"] != nil {
|
if args["status"] != nil {
|
||||||
if args["status"].(bool) {
|
if args["status"].(bool) {
|
||||||
if err = CmdStatus(); err != nil {
|
if err = CmdStatus(); err != nil {
|
||||||
fmt.Printf("frps get status error: %v\n", err)
|
fmt.Printf("frpc get status error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
@ -73,6 +73,16 @@ local_port = 22
|
|||||||
# if remote_port is 0, frps will assgin a random port for you
|
# if remote_port is 0, frps will assgin a random port for you
|
||||||
remote_port = 0
|
remote_port = 0
|
||||||
|
|
||||||
|
# if you want tp expose multiple ports, add 'range:' prefix to the section name
|
||||||
|
# frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on.
|
||||||
|
[range:tcp_port]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 6010-6020,6022,6024-6028
|
||||||
|
remote_port = 6010-6020,6022,6024-6028
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
[dns]
|
[dns]
|
||||||
type = udp
|
type = udp
|
||||||
local_ip = 114.114.114.114
|
local_ip = 114.114.114.114
|
||||||
@ -81,6 +91,14 @@ remote_port = 6002
|
|||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
|
||||||
|
[range:udp_port]
|
||||||
|
type = udp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 6010-6020
|
||||||
|
remote_port = 6010-6020
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||||
[web01]
|
[web01]
|
||||||
type = http
|
type = http
|
||||||
@ -124,6 +142,22 @@ plugin = http_proxy
|
|||||||
plugin_http_user = abc
|
plugin_http_user = abc
|
||||||
plugin_http_passwd = abc
|
plugin_http_passwd = abc
|
||||||
|
|
||||||
|
[plugin_socks5]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6005
|
||||||
|
plugin = socks5
|
||||||
|
plugin_user = abc
|
||||||
|
plugin_passwd = abc
|
||||||
|
|
||||||
|
[plugin_static_file]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6006
|
||||||
|
plugin = static_file
|
||||||
|
plugin_local_path = /var/www/blog
|
||||||
|
plugin_strip_prefix = static
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
|
||||||
[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
|
||||||
|
@ -52,6 +52,9 @@ privilege_allow_ports = 2000-3000,3001,3003,4000-50000
|
|||||||
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
|
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
|
||||||
max_pool_count = 5
|
max_pool_count = 5
|
||||||
|
|
||||||
|
# max ports can be used for each client, default value is 0 means no limit
|
||||||
|
max_ports_per_client = 0
|
||||||
|
|
||||||
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
|
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
|
||||||
# if authentication_timeout is zero, the time is not verified, default is 900s
|
# if authentication_timeout is zero, the time is not verified, default is 900s
|
||||||
authentication_timeout = 900
|
authentication_timeout = 900
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/frp/models/consts"
|
"github.com/fatedier/frp/models/consts"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
|
||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
)
|
)
|
||||||
@ -770,6 +771,38 @@ func (cfg *XtcpProxyConf) Check() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) {
|
||||||
|
localPorts, errRet := util.ParseRangeNumbers(section["local_port"])
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"])
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(localPorts) != len(remotePorts) {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(localPorts) == 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sections = make(map[string]ini.Section)
|
||||||
|
for i, port := range localPorts {
|
||||||
|
subName := fmt.Sprintf("%s_%d", name, i)
|
||||||
|
subSection := copySection(section)
|
||||||
|
subSection["local_port"] = fmt.Sprintf("%d", port)
|
||||||
|
subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i])
|
||||||
|
sections[subName] = subSection
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// if len(startProxy) is 0, start all
|
// if len(startProxy) is 0, start all
|
||||||
// otherwise just start proxies in startProxy map
|
// otherwise just start proxies in startProxy map
|
||||||
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
||||||
@ -786,22 +819,51 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
|
|||||||
proxyConfs = make(map[string]ProxyConf)
|
proxyConfs = make(map[string]ProxyConf)
|
||||||
visitorConfs = make(map[string]ProxyConf)
|
visitorConfs = make(map[string]ProxyConf)
|
||||||
for name, section := range conf {
|
for name, section := range conf {
|
||||||
|
if name == "common" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
_, shouldStart := startProxy[name]
|
_, shouldStart := startProxy[name]
|
||||||
if name != "common" && (startAll || shouldStart) {
|
if !startAll && !shouldStart {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
subSections := make(map[string]ini.Section)
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, "range:") {
|
||||||
|
// range section
|
||||||
|
rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:"))
|
||||||
|
subSections, err = ParseRangeSection(rangePrefix, section)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subSections[name] = section
|
||||||
|
}
|
||||||
|
|
||||||
|
for subName, subSection := range subSections {
|
||||||
// some proxy or visotr configure may be used this prefix
|
// some proxy or visotr configure may be used this prefix
|
||||||
section["prefix"] = prefix
|
subSection["prefix"] = prefix
|
||||||
cfg, err := NewProxyConfFromFile(name, section)
|
cfg, err := NewProxyConfFromFile(subName, subSection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return proxyConfs, visitorConfs, err
|
return proxyConfs, visitorConfs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
role := section["role"]
|
role := subSection["role"]
|
||||||
if role == "visitor" {
|
if role == "visitor" {
|
||||||
visitorConfs[prefix+name] = cfg
|
visitorConfs[prefix+subName] = cfg
|
||||||
} else {
|
} else {
|
||||||
proxyConfs[prefix+name] = cfg
|
proxyConfs[prefix+subName] = cfg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copySection(section ini.Section) (out ini.Section) {
|
||||||
|
out = make(ini.Section)
|
||||||
|
for k, v := range section {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServerCommonCfg *ServerCommonConf
|
var ServerCommonCfg *ServerCommonConf
|
||||||
@ -57,6 +59,7 @@ type ServerCommonConf struct {
|
|||||||
|
|
||||||
PrivilegeAllowPorts map[int]struct{}
|
PrivilegeAllowPorts map[int]struct{}
|
||||||
MaxPoolCount int64
|
MaxPoolCount int64
|
||||||
|
MaxPortsPerClient int64
|
||||||
HeartBeatTimeout int64
|
HeartBeatTimeout int64
|
||||||
UserConnTimeout int64
|
UserConnTimeout int64
|
||||||
}
|
}
|
||||||
@ -87,6 +90,7 @@ func GetDefaultServerCommonConf() *ServerCommonConf {
|
|||||||
TcpMux: true,
|
TcpMux: true,
|
||||||
PrivilegeAllowPorts: make(map[int]struct{}),
|
PrivilegeAllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
|
MaxPortsPerClient: 0,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
}
|
}
|
||||||
@ -238,55 +242,46 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
|||||||
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
|
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
|
||||||
if ok {
|
if ok {
|
||||||
// e.g. 1000-2000,2001,2002,3000-4000
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
portRanges := strings.Split(allowPortsStr, ",")
|
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
|
||||||
for _, portRangeStr := range portRanges {
|
if errRet != nil {
|
||||||
// 1000-2000 or 2001
|
err = fmt.Errorf("Parse conf error: privilege_allow_ports: %v", errRet)
|
||||||
portArray := strings.Split(portRangeStr, "-")
|
return
|
||||||
// length: only 1 or 2 is correct
|
}
|
||||||
rangeType := len(portArray)
|
|
||||||
if rangeType == 1 {
|
for _, port := range ports {
|
||||||
// single port
|
cfg.PrivilegeAllowPorts[int(port)] = struct{}{}
|
||||||
singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
|
|
||||||
if errRet != nil {
|
|
||||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg.PrivilegeAllowPorts[int(singlePort)] = struct{}{}
|
|
||||||
} else if rangeType == 2 {
|
|
||||||
// range ports
|
|
||||||
min, errRet := strconv.ParseInt(portArray[0], 10, 64)
|
|
||||||
if errRet != nil {
|
|
||||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
max, errRet := strconv.ParseInt(portArray[1], 10, 64)
|
|
||||||
if errRet != nil {
|
|
||||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if max < min {
|
|
||||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := min; i <= max; i++ {
|
|
||||||
cfg.PrivilegeAllowPorts[int(i)] = struct{}{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "max_pool_count")
|
tmpStr, ok = conf.Get("common", "max_pool_count")
|
||||||
if ok {
|
if ok {
|
||||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
if err == nil && v >= 0 {
|
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if v < 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
|
||||||
|
return
|
||||||
|
}
|
||||||
cfg.MaxPoolCount = v
|
cfg.MaxPoolCount = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "max_ports_per_client")
|
||||||
|
if ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if v < 0 {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.MaxPortsPerClient = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "authentication_timeout")
|
tmpStr, ok = conf.Get("common", "authentication_timeout")
|
||||||
if ok {
|
if ok {
|
||||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
@ -17,14 +17,11 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/errors"
|
|
||||||
frpIo "github.com/fatedier/frp/utils/io"
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
@ -35,47 +32,6 @@ func init() {
|
|||||||
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
Register(PluginHttpProxy, NewHttpProxyPlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
|
||||||
conns chan net.Conn
|
|
||||||
closed bool
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProxyListener() *Listener {
|
|
||||||
return &Listener{
|
|
||||||
conns: make(chan net.Conn, 64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Accept() (net.Conn, error) {
|
|
||||||
conn, ok := <-l.conns
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("listener closed")
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) PutConn(conn net.Conn) error {
|
|
||||||
err := errors.PanicToError(func() {
|
|
||||||
l.conns <- conn
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Close() error {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
if !l.closed {
|
|
||||||
close(l.conns)
|
|
||||||
l.closed = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Addr() net.Addr {
|
|
||||||
return (*net.TCPAddr)(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
l *Listener
|
l *Listener
|
||||||
s *http.Server
|
s *http.Server
|
||||||
|
@ -17,7 +17,10 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,3 +48,44 @@ type Plugin interface {
|
|||||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
conns chan net.Conn
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyListener() *Listener {
|
||||||
|
return &Listener{
|
||||||
|
conns: make(chan net.Conn, 64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
|
conn, ok := <-l.conns
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("listener closed")
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) PutConn(conn net.Conn) error {
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
l.conns <- conn
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
if !l.closed {
|
||||||
|
close(l.conns)
|
||||||
|
l.closed = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Addr() net.Addr {
|
||||||
|
return (*net.TCPAddr)(nil)
|
||||||
|
}
|
||||||
|
@ -32,13 +32,23 @@ func init() {
|
|||||||
|
|
||||||
type Socks5Plugin struct {
|
type Socks5Plugin struct {
|
||||||
Server *gosocks5.Server
|
Server *gosocks5.Server
|
||||||
|
|
||||||
|
user string
|
||||||
|
passwd string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||||
sp := &Socks5Plugin{}
|
user := params["plugin_user"]
|
||||||
sp.Server, err = gosocks5.New(&gosocks5.Config{
|
passwd := params["plugin_passwd"]
|
||||||
|
|
||||||
|
cfg := &gosocks5.Config{
|
||||||
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
||||||
})
|
}
|
||||||
|
if user != "" || passwd != "" {
|
||||||
|
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
|
||||||
|
}
|
||||||
|
sp := &Socks5Plugin{}
|
||||||
|
sp.Server, err = gosocks5.New(cfg)
|
||||||
p = sp
|
p = sp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
87
models/plugin/static_file.go
Normal file
87
models/plugin/static_file.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginStaticFile = "static_file"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginStaticFile, NewStaticFilePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaticFilePlugin struct {
|
||||||
|
localPath string
|
||||||
|
stripPrefix string
|
||||||
|
httpUser string
|
||||||
|
httpPasswd string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||||
|
localPath := params["plugin_local_path"]
|
||||||
|
stripPrefix := params["plugin_strip_prefix"]
|
||||||
|
httpUser := params["plugin_http_user"]
|
||||||
|
httpPasswd := params["plugin_http_passwd"]
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
sp := &StaticFilePlugin{
|
||||||
|
localPath: localPath,
|
||||||
|
stripPrefix: stripPrefix,
|
||||||
|
httpUser: httpUser,
|
||||||
|
httpPasswd: httpPasswd,
|
||||||
|
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
var prefix string
|
||||||
|
if stripPrefix != "" {
|
||||||
|
prefix = "/" + stripPrefix + "/"
|
||||||
|
} else {
|
||||||
|
prefix = "/"
|
||||||
|
}
|
||||||
|
router := httprouter.New()
|
||||||
|
router.Handler("GET", prefix+"*filepath", frpNet.MakeHttpGzipHandler(
|
||||||
|
frpNet.NewHttpBasicAuthWraper(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))), httpUser, httpPasswd)))
|
||||||
|
sp.s = &http.Server{
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
go sp.s.Serve(listener)
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
sp.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Name() string {
|
||||||
|
return PluginStaticFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StaticFilePlugin) Close() error {
|
||||||
|
sp.s.Close()
|
||||||
|
sp.l.Close()
|
||||||
|
return nil
|
||||||
|
}
|
@ -55,6 +55,9 @@ type Control struct {
|
|||||||
// pool count
|
// pool count
|
||||||
poolCount int
|
poolCount int
|
||||||
|
|
||||||
|
// ports used, for limitations
|
||||||
|
portsUsedNum int
|
||||||
|
|
||||||
// last time got the Ping message
|
// last time got the Ping message
|
||||||
lastPing time.Time
|
lastPing time.Time
|
||||||
|
|
||||||
@ -84,6 +87,7 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
|||||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
||||||
proxies: make(map[string]Proxy),
|
proxies: make(map[string]Proxy),
|
||||||
poolCount: loginMsg.PoolCount,
|
poolCount: loginMsg.PoolCount,
|
||||||
|
portsUsedNum: 0,
|
||||||
lastPing: time.Now(),
|
lastPing: time.Now(),
|
||||||
runId: loginMsg.RunId,
|
runId: loginMsg.RunId,
|
||||||
status: consts.Working,
|
status: consts.Working,
|
||||||
@ -348,6 +352,26 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
|||||||
return remoteAddr, err
|
return remoteAddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check ports used number in each client
|
||||||
|
if config.ServerCommonCfg.MaxPortsPerClient > 0 {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(config.ServerCommonCfg.MaxPortsPerClient) {
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
err = fmt.Errorf("exceed the max_ports_per_client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum()
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
remoteAddr, err = pxy.Run()
|
remoteAddr, err = pxy.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -371,16 +395,21 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
|||||||
|
|
||||||
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||||
ctl.mu.Lock()
|
ctl.mu.Lock()
|
||||||
defer ctl.mu.Unlock()
|
|
||||||
|
|
||||||
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
ctl.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.ServerCommonCfg.MaxPortsPerClient > 0 {
|
||||||
|
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
|
||||||
|
}
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
ctl.svr.DelProxy(pxy.GetName())
|
ctl.svr.DelProxy(pxy.GetName())
|
||||||
delete(ctl.proxies, closeMsg.ProxyName)
|
delete(ctl.proxies, closeMsg.ProxyName)
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,15 +40,18 @@ type Proxy interface {
|
|||||||
GetName() string
|
GetName() string
|
||||||
GetConf() config.ProxyConf
|
GetConf() config.ProxyConf
|
||||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
||||||
|
GetUsedPortsNum() int
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
name string
|
name string
|
||||||
ctl *Control
|
ctl *Control
|
||||||
listeners []frpNet.Listener
|
listeners []frpNet.Listener
|
||||||
mu sync.RWMutex
|
usedPortsNum int
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +63,10 @@ func (pxy *BaseProxy) GetControl() *Control {
|
|||||||
return pxy.ctl
|
return pxy.ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||||
|
return pxy.usedPortsNum
|
||||||
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) Close() {
|
func (pxy *BaseProxy) Close() {
|
||||||
pxy.Info("proxy closing")
|
pxy.Info("proxy closing")
|
||||||
for _, l := range pxy.listeners {
|
for _, l := range pxy.listeners {
|
||||||
@ -126,6 +133,7 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
|||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &TcpProxy{
|
pxy = &TcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@ -141,6 +149,7 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
@ -161,3 +161,9 @@ remote_port = 0
|
|||||||
type = tcp
|
type = tcp
|
||||||
plugin = http_proxy
|
plugin = http_proxy
|
||||||
remote_port = 0
|
remote_port = 0
|
||||||
|
|
||||||
|
[range:range_tcp]
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 30000-30001,30003
|
||||||
|
remote_port = 30000-30001,30003
|
||||||
|
@ -5,5 +5,5 @@ vhost_http_port = 10804
|
|||||||
log_file = ./frps.log
|
log_file = ./frps.log
|
||||||
log_level = debug
|
log_level = debug
|
||||||
privilege_token = 123456
|
privilege_token = 123456
|
||||||
privilege_allow_ports = 10000-20000,20002,30000-40000
|
privilege_allow_ports = 10000-20000,20002,30000-50000
|
||||||
subdomain_host = sub.com
|
subdomain_host = sub.com
|
||||||
|
@ -53,8 +53,9 @@ var (
|
|||||||
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
|
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
|
||||||
ProxyUdpPortNormal string = "udp_port_normal"
|
ProxyUdpPortNormal string = "udp_port_normal"
|
||||||
ProxyUdpRandomPort string = "udp_random_port"
|
ProxyUdpRandomPort string = "udp_random_port"
|
||||||
|
ProxyHttpProxy string = "http_proxy"
|
||||||
|
|
||||||
ProxyHttpProxy string = "http_proxy"
|
ProxyRangeTcpPrefix string = "range_tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -286,3 +287,15 @@ func TestPluginHttpProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRangePortsMapping(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
name := fmt.Sprintf("%s_%d", ProxyRangeTcpPrefix, i)
|
||||||
|
status, err := getProxyStatus(name)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(client.ProxyStatusRunning, status.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RandId return a rand string used in frp.
|
// RandId return a rand string used in frp.
|
||||||
@ -54,3 +56,48 @@ func CanonicalAddr(host string, port int) (addr string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
|
||||||
|
rangeStr = strings.TrimSpace(rangeStr)
|
||||||
|
numbers = make([]int64, 0)
|
||||||
|
// e.g. 1000-2000,2001,2002,3000-4000
|
||||||
|
numRanges := strings.Split(rangeStr, ",")
|
||||||
|
for _, numRangeStr := range numRanges {
|
||||||
|
// 1000-2000 or 2001
|
||||||
|
numArray := strings.Split(numRangeStr, "-")
|
||||||
|
// length: only 1 or 2 is correct
|
||||||
|
rangeType := len(numArray)
|
||||||
|
if rangeType == 1 {
|
||||||
|
// single number
|
||||||
|
singleNum, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("range number is invalid, %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numbers = append(numbers, singleNum)
|
||||||
|
} else if rangeType == 2 {
|
||||||
|
// range numbers
|
||||||
|
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("range number is invalid, %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||||
|
if errRet != nil {
|
||||||
|
err = fmt.Errorf("range number is invalid, %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if max < min {
|
||||||
|
err = fmt.Errorf("range number is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := min; i <= max; i++ {
|
||||||
|
numbers = append(numbers, i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("range number is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -20,3 +20,29 @@ func TestGetAuthKey(t *testing.T) {
|
|||||||
t.Log(key)
|
t.Log(key)
|
||||||
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRangeNumbers(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
numbers, err := ParseRangeNumbers("2-5")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal([]int64{2, 3, 4, 5}, numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers, err = ParseRangeNumbers("1")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal([]int64{1}, numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers, err = ParseRangeNumbers("3-5,8")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal([]int64{3, 4, 5, 8}, numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ParseRangeNumbers("3-a")
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.15.1"
|
var version string = "0.16.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
Loading…
Reference in New Issue
Block a user