release v0.5.0 (#24)

[new] Optimize for http services.Support virtual host and custom domain binding.
[new] Support max days of keeping log files.
[fix] Fix a bug when reconnecting.
pull/27/head v0.5.0
fatedier 2016-05-03 22:05:55 +08:00
parent 2c39719cc0
commit f804330dbf
18 changed files with 607 additions and 140 deletions

View File

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

View File

@ -8,6 +8,12 @@
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
## What can I do with frp?
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
* Inspect all http requests/responses that are transmitted over the tunnel(future).
## Status ## Status
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing. frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
@ -16,17 +22,15 @@ frp is under development and you can try it with latest release version.Master b
## Quick Start ## Quick Start
Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) Read the [QuickStart](/doc/quick_start_en.md)
[Tcp port forwarding](/doc/quick_start_en.md#tcp-port-forwarding)
[Http port forwarding and Custom domain binding](/doc/quick_start_en.md#http-port-forwarding-and-custom-domains-binding)
## Architecture ## Architecture
![architecture](doc/pic/architecture.png) ![architecture](/doc/pic/architecture.png)
## What can I do with frp?
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address.
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
* Inspect all http requests/responses that are transmitted over the tunnel(future).
## Contributing ## Contributing

View File

@ -4,31 +4,36 @@
[README](README.md) | [中文文档](README_zh.md) [README](README.md) | [中文文档](README_zh.md)
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务。 >frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务,对于 http 服务还支持虚拟主机功能访问80端口可以根据域名路由到后端不同的 http 服务。
## frp 的作用?
* 利用处于内网或防火墙后的机器,对外网环境提供 http 服务。
* 对于 http 服务支持基于域名的虚拟主机支持自定义域名绑定使多个域名可以共用一个80端口。
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问公司局域网内的主机。
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
## 开发状态 ## 开发状态
frp 目前正在前期开发阶段master分支用于发布稳定版本dev分支用于开发您可以尝试下载最新的 release 版本进行测试。 frp 目前正在前期开发阶段master分支用于发布稳定版本dev分支用于开发您可以尝试下载最新的 release 版本进行测试。
**在 1.x 版本以前,交互协议都可能会被改变,不能保证向后兼容。** **在 1.0 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
## 快速开始 ## 快速开始
[QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) [使用文档](/doc/quick_start_zh.md)
[tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发)
[http 端口转发,自定义域名绑定](/doc/quick_start_zh.md#http-端口转发自定义域名绑定)
## 架构 ## 架构
![architecture](doc/pic/architecture.png) ![architecture](/doc/pic/architecture.png)
## frp 的作用?
* 利用处于内网或防火墙后的机器对外网环境提供http服务。针对http的优化正在开发中
* 利用处于内网或防火墙后的机器对外网环境提供tcp服务。
* 可查看通过代理的所有http请求和响应信息。待开发
## 贡献代码 ## 贡献代码
如果您对这个项目感兴趣,并且想要参与其中,我们非常欢迎! 如果您对这个项目感兴趣,我们非常欢迎您参与其中!
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。 * 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。 * 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。

View File

@ -6,12 +6,27 @@ server_port = 7000
log_file = ./frpc.log log_file = ./frpc.log
# debug, info, warn, error # debug, info, warn, error
log_level = info log_level = info
log_max_days = 3
# for authentication # for authentication
auth_token = 123 auth_token = 123
# test1 is the proxy name same as server's configuration # ssh is the proxy name same as server's configuration
[test1] [ssh]
# tcp | http, default is tcp
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 = true
# 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, the domains are set in frps.ini
[web01]
type = http
local_ip = 127.0.0.1
local_port = 80
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8000

View File

@ -2,13 +2,28 @@
[common] [common]
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
bind_port = 7000 bind_port = 7000
# optional
vhost_http_port = 80
# console or real logFile path like ./frps.log # console or real logFile path like ./frps.log
log_file = ./frps.log log_file = ./frps.log
# debug, info, warn, error # debug, info, warn, error
log_level = info log_level = info
log_max_days = 3
# test1 is the proxy name, client will use this name and auth_token to connect to server # ssh is the proxy name, client will use this name and auth_token to connect to server
[test1] [ssh]
type = tcp
auth_token = 123 auth_token = 123
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
listen_port = 6000 listen_port = 6000
[web01]
type = http
auth_token = 123
# if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com

View File

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

View File

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

View File

@ -88,7 +88,7 @@ func main() {
client.ServerPort = serverPort client.ServerPort = serverPort
} }
log.InitLog(client.LogWay, client.LogFile, client.LogLevel) log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
// wait until all control goroutine exit // wait until all control goroutine exit
var wait sync.WaitGroup var wait sync.WaitGroup

View File

@ -30,7 +30,7 @@ import (
func ProcessControlConn(l *conn.Listener) { func ProcessControlConn(l *conn.Listener) {
for { for {
c, err := l.GetConn() c, err := l.Accept()
if err != nil { if err != nil {
return return
} }

View File

@ -19,6 +19,7 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
docopt "github.com/docopt/docopt-go" docopt "github.com/docopt/docopt-go"
@ -26,6 +27,7 @@ import (
"frp/utils/conn" "frp/utils/conn"
"frp/utils/log" "frp/utils/log"
"frp/utils/version" "frp/utils/version"
"frp/utils/vhost"
) )
var ( var (
@ -88,12 +90,25 @@ func main() {
server.BindPort = bindPort server.BindPort = bindPort
} }
log.InitLog(server.LogWay, server.LogFile, server.LogLevel) log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
l, err := conn.Listen(server.BindAddr, server.BindPort) l, err := conn.Listen(server.BindAddr, server.BindPort)
if err != nil { if err != nil {
log.Error("Create listener error, %v", err) log.Error("Create server listener error, %v", err)
os.Exit(-1) os.Exit(1)
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
if err != nil {
log.Error("Create vhost http listener error, %v", err)
os.Exit(1)
}
server.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpMuxer error, %v", err)
}
} }
log.Info("Start frps success") log.Info("Start frps success")

View File

@ -31,6 +31,7 @@ type ProxyClient struct {
AuthToken string AuthToken string
LocalIp string LocalIp string
LocalPort int64 LocalPort int64
Type string
UseEncryption bool UseEncryption bool
} }

View File

@ -28,6 +28,7 @@ var (
LogFile string = "console" LogFile string = "console"
LogWay string = "console" LogWay string = "console"
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3
HeartBeatInterval int64 = 20 HeartBeatInterval int64 = 20
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
) )
@ -69,6 +70,11 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr LogLevel = tmpStr
} }
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
var authToken string var authToken string
tmpStr, ok = conf.Get("common", "auth_token") tmpStr, ok = conf.Get("common", "auth_token")
if ok { if ok {
@ -105,6 +111,16 @@ func LoadConf(confFile string) (err error) {
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name) return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
} }
// type
proxyClient.Type = "tcp"
typeStr, ok := section["type"]
if ok {
if typeStr != "tcp" && typeStr != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
}
proxyClient.Type = typeStr
}
// use_encryption // use_encryption
proxyClient.UseEncryption = false proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"] useEncryptionStr, ok := section["use_encryption"]

View File

@ -17,19 +17,26 @@ package server
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"frp/utils/vhost"
) )
// common config // common config
var ( var (
BindAddr string = "0.0.0.0" BindAddr string = "0.0.0.0"
BindPort int64 = 7000 BindPort int64 = 7000
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, do not listen a public port for http
LogFile string = "console" LogFile string = "console"
LogWay string = "console" // console or file LogWay string = "console" // console or file
LogLevel string = "info" LogLevel string = "info"
LogMaxDays int64 = 3
HeartBeatTimeout int64 = 90 HeartBeatTimeout int64 = 90
UserConnTimeout int64 = 10 UserConnTimeout int64 = 10
VhostMuxer *vhost.HttpMuxer
) )
var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer) var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer)
@ -54,6 +61,13 @@ func LoadConf(confFile string) (err error) {
BindPort, _ = strconv.ParseInt(tmpStr, 10, 64) BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} }
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "log_file") tmpStr, ok = conf.Get("common", "log_file")
if ok { if ok {
LogFile = tmpStr LogFile = tmpStr
@ -69,17 +83,34 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr LogLevel = tmpStr
} }
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
// servers // servers
for name, section := range conf { for name, section := range conf {
if name != "common" { if name != "common" {
proxyServer := &ProxyServer{} proxyServer := &ProxyServer{}
proxyServer.CustomDomains = make([]string, 0)
proxyServer.Name = name proxyServer.Name = name
proxyServer.Type, ok = section["type"]
if ok {
if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyServer.Name)
}
} else {
proxyServer.Type = "tcp"
}
proxyServer.AuthToken, ok = section["auth_token"] proxyServer.AuthToken, ok = section["auth_token"]
if !ok { if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name) return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
} }
// for tcp
if proxyServer.Type == "tcp" {
proxyServer.BindAddr, ok = section["bind_addr"] proxyServer.BindAddr, ok = section["bind_addr"]
if !ok { if !ok {
proxyServer.BindAddr = "0.0.0.0" proxyServer.BindAddr = "0.0.0.0"
@ -94,6 +125,20 @@ func LoadConf(confFile string) (err error) {
} else { } else {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name) return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
} }
} else if proxyServer.Type == "http" {
// for http
domainStr, ok := section["custom_domains"]
if ok {
var suffix string
if VhostHttpPort != 80 {
suffix = fmt.Sprintf(":%d", VhostHttpPort)
}
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + suffix
}
}
}
proxyServer.Init() proxyServer.Init()
ProxyServers[proxyServer.Name] = proxyServer ProxyServers[proxyServer.Name] = proxyServer

View File

@ -24,15 +24,22 @@ import (
"frp/utils/log" "frp/utils/log"
) )
type Listener interface {
Accept() (*conn.Conn, error)
Close() error
}
type ProxyServer struct { type ProxyServer struct {
Name string Name string
AuthToken string AuthToken string
UseEncryption bool Type string
BindAddr string BindAddr string
ListenPort int64 ListenPort int64
Status int64 UseEncryption bool
CustomDomains []string
listener *conn.Listener // accept new connection from remote users Status int64
listeners []Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
workConnChan chan *conn.Conn // get new work conns from control goroutine workConnChan chan *conn.Conn // get new work conns from control goroutine
userConnList *list.List // store user conns userConnList *list.List // store user conns
@ -44,6 +51,7 @@ func (p *ProxyServer) Init() {
p.workConnChan = make(chan *conn.Conn) p.workConnChan = make(chan *conn.Conn)
p.ctlMsgChan = make(chan int64) p.ctlMsgChan = make(chan int64)
p.userConnList = list.New() p.userConnList = list.New()
p.listeners = make([]Listener, 0)
} }
func (p *ProxyServer) Lock() { func (p *ProxyServer) Lock() {
@ -57,19 +65,31 @@ func (p *ProxyServer) Unlock() {
// start listening for user conns // start listening for user conns
func (p *ProxyServer) Start() (err error) { func (p *ProxyServer) Start() (err error) {
p.Init() p.Init()
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort) if p.Type == "tcp" {
l, err := conn.Listen(p.BindAddr, p.ListenPort)
if err != nil { if err != nil {
return err return err
} }
p.listeners = append(p.listeners, l)
} else if p.Type == "http" {
for _, domain := range p.CustomDomains {
l, err := VhostMuxer.Listen(domain)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
}
}
p.Status = consts.Working p.Status = consts.Working
// start a goroutine for listener to accept user connection // start a goroutine for listener to accept user connection
go func() { for _, listener := range p.listeners {
go func(l Listener) {
for { for {
// block // block
// if listener is closed, err returned // if listener is closed, err returned
c, err := p.listener.GetConn() c, err := l.Accept()
if err != nil { if err != nil {
log.Info("ProxyName [%s], listener is closed", p.Name) log.Info("ProxyName [%s], listener is closed", p.Name)
return return
@ -93,8 +113,8 @@ func (p *ProxyServer) Start() (err error) {
// set timeout // set timeout
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() { time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
p.Lock() p.Lock()
defer p.Unlock()
element := p.userConnList.Front() element := p.userConnList.Front()
p.Unlock()
if element == nil { if element == nil {
return return
} }
@ -102,12 +122,14 @@ func (p *ProxyServer) Start() (err error) {
userConn := element.Value.(*conn.Conn) userConn := element.Value.(*conn.Conn)
if userConn == c { if userConn == c {
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr()) log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
userConn.Close()
} }
}) })
} }
}() }(listener)
}
// start another goroutine for join two conns from client and user // start another goroutine for join two conns from frpc and user
go func() { go func() {
for { for {
workConn, ok := <-p.workConnChan workConn, ok := <-p.workConnChan
@ -149,8 +171,12 @@ func (p *ProxyServer) Close() {
p.Lock() p.Lock()
if p.Status != consts.Closed { if p.Status != consts.Closed {
p.Status = consts.Closed p.Status = consts.Closed
if p.listener != nil { if len(p.listeners) != 0 {
p.listener.Close() for _, l := range p.listeners {
if l != nil {
l.Close()
}
}
} }
close(p.ctlMsgChan) close(p.ctlMsgChan)
close(p.workConnChan) close(p.workConnChan)

View File

@ -20,6 +20,7 @@ import (
"io" "io"
"net" "net"
"sync" "sync"
"time"
"frp/utils/log" "frp/utils/log"
"frp/utils/pcrypto" "frp/utils/pcrypto"
@ -28,7 +29,7 @@ import (
type Listener struct { type Listener struct {
addr net.Addr addr net.Addr
l *net.TCPListener l *net.TCPListener
conns chan *Conn accept chan *Conn
closeFlag bool closeFlag bool
} }
@ -42,7 +43,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
l = &Listener{ l = &Listener{
addr: listener.Addr(), addr: listener.Addr(),
l: listener, l: listener,
conns: make(chan *Conn), accept: make(chan *Conn),
closeFlag: false, closeFlag: false,
} }
@ -61,7 +62,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
closeFlag: false, closeFlag: false,
} }
c.Reader = bufio.NewReader(c.TcpConn) c.Reader = bufio.NewReader(c.TcpConn)
l.conns <- c l.accept <- c
} }
}() }()
return l, err return l, err
@ -69,30 +70,38 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
// wait util get one new connection or listener is closed // wait util get one new connection or listener is closed
// if listener is closed, err returned // if listener is closed, err returned
func (l *Listener) GetConn() (conn *Conn, err error) { func (l *Listener) Accept() (*Conn, error) {
var ok bool conn, ok := <-l.accept
conn, ok = <-l.conns
if !ok { if !ok {
return conn, fmt.Errorf("channel close") return conn, fmt.Errorf("channel close")
} }
return conn, nil return conn, nil
} }
func (l *Listener) Close() { func (l *Listener) Close() error {
if l.l != nil && l.closeFlag == false { if l.l != nil && l.closeFlag == false {
l.closeFlag = true l.closeFlag = true
l.l.Close() l.l.Close()
close(l.conns) close(l.accept)
} }
return nil
} }
// wrap for TCPConn // wrap for TCPConn
type Conn struct { type Conn struct {
TcpConn *net.TCPConn TcpConn net.Conn
Reader *bufio.Reader Reader *bufio.Reader
closeFlag bool closeFlag bool
} }
func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{}
c.TcpConn = conn
c.Reader = bufio.NewReader(c.TcpConn)
c.closeFlag = false
return c
}
func ConnectServer(host string, port int64) (c *Conn, err error) { func ConnectServer(host string, port int64) (c *Conn, err error) {
c = &Conn{} c = &Conn{}
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port)) servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
@ -131,6 +140,11 @@ func (c *Conn) Write(content string) (err error) {
} }
func (c *Conn) SetDeadline(t time.Time) error {
err := c.TcpConn.SetDeadline(t)
return err
}
func (c *Conn) Close() { func (c *Conn) Close() {
if c.TcpConn != nil && c.closeFlag == false { if c.TcpConn != nil && c.closeFlag == false {
c.closeFlag = true c.closeFlag = true

View File

@ -15,6 +15,7 @@
package log package log
import ( import (
"fmt"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
) )
@ -26,24 +27,24 @@ func init() {
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
} }
func InitLog(logWay string, logFile string, logLevel string) { func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
SetLogFile(logWay, logFile) SetLogFile(logWay, logFile, maxdays)
SetLogLevel(logLevel) SetLogLevel(logLevel)
} }
// logWay: such as file or console // logWay: file or console
func SetLogFile(logWay string, logFile string) { func SetLogFile(logWay string, logFile string, maxdays int64) {
if logWay == "console" { if logWay == "console" {
Log.SetLogger("console", "") Log.SetLogger("console", "")
} else { } else {
Log.SetLogger("file", `{"filename": "`+logFile+`"}`) params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
Log.SetLogger("file", params)
} }
} }
// value: error, warning, info, debug // value: error, warning, info, debug
func SetLogLevel(logLevel string) { func SetLogLevel(logLevel string) {
level := 4 // warning level := 4 // warning
switch logLevel { switch logLevel {
case "error": case "error":
level = 3 level = 3
@ -56,7 +57,6 @@ func SetLogLevel(logLevel string) {
default: default:
level = 4 level = 4
} }
Log.SetLevel(level) Log.SetLevel(level)
} }

View File

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

View File

@ -0,0 +1,193 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vhost
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"frp/utils/conn"
)
type muxFunc func(*conn.Conn) (net.Conn, string, error)
type VhostMuxer struct {
listener *conn.Listener
timeout time.Duration
vhostFunc muxFunc
registryMap map[string]*Listener
mutex sync.RWMutex
}
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{
listener: listener,
timeout: timeout,
vhostFunc: vhostFunc,
registryMap: make(map[string]*Listener),
}
go mux.run()
return mux, nil
}
func (v *VhostMuxer) Listen(name string) (l *Listener, err error) {
v.mutex.Lock()
defer v.mutex.Unlock()
if _, exist := v.registryMap[name]; exist {
return nil, fmt.Errorf("name %s is already bound", name)
}
l = &Listener{
name: name,
mux: v,
accept: make(chan *conn.Conn),
}
v.registryMap[name] = l
return l, nil
}
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
v.mutex.RLock()
defer v.mutex.RUnlock()
l, exist = v.registryMap[name]
return l, exist
}
func (v *VhostMuxer) unRegister(name string) {
v.mutex.Lock()
defer v.mutex.Unlock()
delete(v.registryMap, name)
}
func (v *VhostMuxer) run() {
for {
conn, err := v.listener.Accept()
if err != nil {
return
}
go v.handle(conn)
}
}
func (v *VhostMuxer) handle(c *conn.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
return
}
sConn, name, err := v.vhostFunc(c)
if err != nil {
return
}
name = strings.ToLower(name)
l, ok := v.getListener(name)
if !ok {
return
}
if err = sConn.SetDeadline(time.Time{}); err != nil {
return
}
c.TcpConn = sConn
l.accept <- c
}
type HttpMuxer struct {
*VhostMuxer
}
func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
sc, rd := newShareConn(c.TcpConn)
request, err := http.ReadRequest(bufio.NewReader(rd))
if err != nil {
return sc, "", err
}
routerName = request.Host
request.Body.Close()
return sc, routerName, nil
}
func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout)
return &HttpMuxer{mux}, err
}
type Listener struct {
name string
mux *VhostMuxer // for closing VhostMuxer
accept chan *conn.Conn
}
func (l *Listener) Accept() (*conn.Conn, error) {
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
}
return conn, nil
}
func (l *Listener) Close() error {
l.mux.unRegister(l.name)
close(l.accept)
return nil
}
func (l *Listener) Name() string {
return l.name
}
type sharedConn struct {
net.Conn
sync.Mutex
buff *bytes.Buffer
}
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
sc := &sharedConn{
Conn: conn,
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
}
return sc, io.TeeReader(conn, sc.buff)
}
func (sc *sharedConn) Read(p []byte) (n int, err error) {
sc.Lock()
if sc.buff == nil {
sc.Unlock()
return sc.Conn.Read(p)
}
n, err = sc.buff.Read(p)
if err == io.EOF {
sc.buff = nil
var n2 int
n2, err = sc.Conn.Read(p[n:])
n += n2
}
sc.Unlock()
return
}