mirror of
https://github.com/fatedier/frp.git
synced 2025-07-29 09:18:11 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f804330dbf | ||
|
2c39719cc0 | ||
|
6874688e07 | ||
|
fdd7436736 | ||
|
0f326449e8 | ||
|
7c3e00ed28 | ||
|
d5913fc77b | ||
|
2ba84d375a | ||
|
6a0d6035cb | ||
|
d091e0eac9 | ||
|
bc176b90f1 | ||
|
a729a4fafe | ||
|
78c770d37d | ||
|
718e707b77 | ||
|
b3ee746be8 | ||
|
80fc76da52 | ||
|
52f99bbc00 | ||
|
45c21b2705 | ||
|
b6212afb03 | ||
|
49975c4c1b | ||
|
580e75f633 | ||
|
20afe25ef1 | ||
|
6e57135533 | ||
|
931c102668 | ||
|
5700101c0e | ||
|
90349a48b0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,6 +25,7 @@ _testmain.go
|
||||
|
||||
# Self
|
||||
bin/
|
||||
packages/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
@@ -3,7 +3,7 @@ language: go
|
||||
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.1
|
||||
- 1.5.3
|
||||
|
||||
install:
|
||||
- make
|
||||
|
14
Dockerfile
14
Dockerfile
@@ -1,14 +0,0 @@
|
||||
FROM golang:1.5
|
||||
|
||||
MAINTAINER fatedier
|
||||
|
||||
RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[wiki]\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"]
|
13
Makefile
13
Makefile
@@ -1,21 +1,22 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export NEW_GOPATH := $(shell pwd)
|
||||
export OLDGOPATH := $(GOPATH)
|
||||
export GOPATH := $(shell pwd):$(GOPATH)
|
||||
|
||||
all: build
|
||||
|
||||
build: godep fmt frps frpc
|
||||
|
||||
godep:
|
||||
@go get github.com/tools/godep
|
||||
GOPATH=$(OLDGOPATH) go get github.com/tools/godep
|
||||
|
||||
fmt:
|
||||
GOPATH=$(NEW_GOPATH) godep go fmt ./...
|
||||
godep go fmt ./...
|
||||
|
||||
frps:
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps
|
||||
godep go build -o bin/frps ./src/frp/cmd/frps
|
||||
|
||||
frpc:
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc
|
||||
godep go build -o bin/frpc ./src/frp/cmd/frpc
|
||||
|
||||
test:
|
||||
@GOPATH=$(NEW_GOPATH) godep go test -v ./...
|
||||
godep go test -v ./...
|
||||
|
15
Makefile.cross-compiles
Normal file
15
Makefile.cross-compiles
Normal file
@@ -0,0 +1,15 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export OLDGOPATH := $(GOPATH)
|
||||
export GOPATH := $(shell pwd)/Godeps/_workspace:$(shell pwd):$(GOPATH)
|
||||
export OS_TARGETS=linux windows
|
||||
export ARCH_TARGETS=386 amd64
|
||||
|
||||
all: build
|
||||
|
||||
build: godep app
|
||||
|
||||
godep:
|
||||
GOPATH=$(OLDGOPATH) go get github.com/mitchellh/gox
|
||||
|
||||
app:
|
||||
gox -os "$(OS_TARGETS)" -arch="$(ARCH_TARGETS)" ./...
|
45
README.md
45
README.md
@@ -2,32 +2,45 @@
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy which can help you expose a local server behind a NAT or firewall to the internet.
|
||||
|
||||
## Status
|
||||
|
||||
frp is under development and you can try it with available version 0.1.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md)
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
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.
|
||||
* 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
|
||||
|
||||
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
|
||||
|
||||
**We may change any protocol and can't promise backward compatible before version 1.x.**
|
||||
|
||||
## Quick Start
|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
## Contributing
|
||||
|
||||
Interested in getting involved? We would love to help you!
|
||||
|
||||
For simple bug fixes, just submit a PR with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an issue.
|
||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch
|
||||
* If you have some wanderful ideas, send email to fatedier@gmail.com.
|
||||
|
||||
If you have some wanderful ideas, send email to fatedier@gmail.com.
|
||||
## Contributors
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
||||
|
45
README_zh.md
Normal file
45
README_zh.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# frp
|
||||
|
||||
[](https://travis-ci.org/fatedier/frp)
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务,对于 http 服务还支持虚拟主机功能,访问80端口,可以根据域名路由到后端不同的 http 服务。
|
||||
|
||||
## frp 的作用?
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 服务。
|
||||
* 对于 http 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问公司局域网内的主机。
|
||||
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
**在 1.0 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
|
||||
|
||||
## 快速开始
|
||||
|
||||
[使用文档](/doc/quick_start_zh.md)
|
||||
|
||||
[tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发)
|
||||
|
||||
[http 端口转发,自定义域名绑定](/doc/quick_start_zh.md#http-端口转发自定义域名绑定)
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||
|
||||
## 贡献代码
|
||||
|
||||
如果您对这个项目感兴趣,我们非常欢迎您参与其中!
|
||||
|
||||
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
|
||||
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。
|
||||
|
||||
## 贡献者
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
@@ -3,12 +3,30 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = console
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
log_level = info
|
||||
log_max_days = 3
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# test1 is the proxy name same as server's configuration
|
||||
[test1]
|
||||
passwd = 123
|
||||
# ssh is the proxy name same as server's configuration
|
||||
[ssh]
|
||||
# tcp | http, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
|
||||
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
|
||||
|
@@ -2,13 +2,28 @@
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# optional
|
||||
vhost_http_port = 80
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = console
|
||||
log_file = ./frps.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
log_level = info
|
||||
log_max_days = 3
|
||||
|
||||
# test1 is the proxy name, client will use this name and passwd to connect to server
|
||||
[test1]
|
||||
passwd = 123
|
||||
# ssh is the proxy name, client will use this name and auth_token to connect to server
|
||||
[ssh]
|
||||
type = tcp
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
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
|
||||
|
45
cross_compiles_package.sh
Executable file
45
cross_compiles_package.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
# compile for version
|
||||
make
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "make error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
frp_version=`./bin/frps --version`
|
||||
echo "build version: $frp_version"
|
||||
|
||||
# cross_compiles
|
||||
make -f ./Makefile.cross-compiles
|
||||
|
||||
rm -rf ./packages
|
||||
mkdir ./packages
|
||||
|
||||
os_all='linux windows'
|
||||
arch_all='386 amd64'
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
frp_dir_name="frp_${frp_version}_${os}_${arch}"
|
||||
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
|
||||
mkdir ${frp_path}
|
||||
if [ "x${os}" = x"windows" ]; then
|
||||
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
|
||||
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
|
||||
else
|
||||
mv ./frpc_${os}_${arch} ${frp_path}/frpc
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ./LICENSE ${frp_path}
|
||||
cp ./conf/* ${frp_path}
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
if [ "x${os}" = x"windows" ]; then
|
||||
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
|
||||
else
|
||||
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
|
||||
fi
|
||||
cd ..
|
||||
rm -rf ${frp_path}
|
||||
done
|
||||
done
|
@@ -2,7 +2,10 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
|
||||
|
||||
### Compile
|
||||
|
||||
Enter the root directory and execute `make`, then wait until finished.
|
||||
@@ -19,16 +24,18 @@ Enter the root directory and execute `make`, then wait until finished.
|
||||
### Pre-requirement
|
||||
|
||||
* 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
|
||||
|
||||
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.
|
||||
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
|
||||
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
|
||||
3. Modify all configuration files, details in next paragraph.
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in server B.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in server A.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in server A).
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
|
||||
|
||||
## Tcp port forwarding
|
||||
|
||||
### Configuration files
|
||||
|
||||
@@ -42,9 +49,9 @@ bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# test is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[test]
|
||||
passwd = 123
|
||||
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# finally we connect to server A by this port
|
||||
listen_port = 6000
|
||||
@@ -59,10 +66,70 @@ server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# test is proxy name same with configure in frps.ini
|
||||
[test]
|
||||
passwd = 123
|
||||
# ssh is proxy name same with configure in frps.ini
|
||||
[ssh]
|
||||
# local port which need to be transferred
|
||||
local_port = 22
|
||||
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## Http port forwarding and Custom domains binding
|
||||
|
||||
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
|
||||
|
||||
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
|
||||
|
||||
After that, you can visit your web pages in local server by custom domains.
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# if you want to support vhost, specify one port for http services
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
auth_token = 123
|
||||
# # if proxy type equals http, custom_domains must be set separated by commas
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
# custom domains are set in frps.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# encryption is optional, default is false
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
|
@@ -1,6 +1,9 @@
|
||||
# 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` 拷贝到相应目录下。
|
||||
|
||||
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
|
||||
|
||||
### 编译
|
||||
|
||||
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
|
||||
@@ -17,16 +22,20 @@ frp 相比于其他项目而言非常易于部署和使用,这里我们用一
|
||||
### 依赖
|
||||
|
||||
* go 1.4 以上版本
|
||||
* godep (如果检查不存在,编译时会通过 go get 命令安装)
|
||||
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
|
||||
|
||||
### 部署
|
||||
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至服务器B任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至服务器A任意目录。
|
||||
3. 修改两边的配置文件,见下一节说明。
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
|
||||
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
|
||||
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。
|
||||
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接服务器A({user}替换为服务器A上存在的真实用户)。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**({user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
|
||||
|
||||
## tcp 端口转发
|
||||
|
||||
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
|
||||
|
||||
### 配置文件
|
||||
|
||||
@@ -40,9 +49,9 @@ bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# test 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[test]
|
||||
passwd = 123
|
||||
# ssh 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# 最后将通过此端口访问后端服务
|
||||
listen_port = 6000
|
||||
@@ -57,10 +66,72 @@ server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# 用于身份验证
|
||||
auth_token = 123
|
||||
|
||||
# test需要和 frps.ini 中配置一致
|
||||
[test]
|
||||
passwd = 123
|
||||
# ssh 需要和 frps.ini 中配置一致
|
||||
[ssh]
|
||||
# 需要转发的本地端口
|
||||
local_port = 22
|
||||
# 启用加密,frpc与frps之间通信加密,默认为 false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## http 端口转发,自定义域名绑定
|
||||
|
||||
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口,则需要指定代理的类型为 http,并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
|
||||
|
||||
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
|
||||
|
||||
之后就可以通过自定义域名访问到本地的多个 web 服务。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# 如果需要支持http类型的代理则需要指定一个端口
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
# type 默认为 tcp,这里需要特别指定为 http
|
||||
type = http
|
||||
auth_token = 123
|
||||
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
|
||||
# 自定义域名在 frps.ini 中配置,方便做统一管理
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# 可选是否加密
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
|
@@ -26,66 +26,105 @@ import (
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
var connection *conn.Conn = nil
|
||||
var heartBeatTimer *time.Timer = nil
|
||||
|
||||
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
|
||||
defer wait.Done()
|
||||
|
||||
msgSendChan := make(chan interface{}, 1024)
|
||||
|
||||
c, err := loginToServer(cli)
|
||||
if err != nil {
|
||||
log.Error("ProxyName [%s], connect to server failed!", cli.Name)
|
||||
return
|
||||
}
|
||||
connection = c
|
||||
defer connection.Close()
|
||||
defer c.Close()
|
||||
|
||||
go heartbeatSender(c, msgSendChan)
|
||||
|
||||
go msgSender(cli, c, msgSendChan)
|
||||
msgReader(cli, c, msgSendChan)
|
||||
|
||||
close(msgSendChan)
|
||||
}
|
||||
|
||||
// loop for reading messages from frpc after control connection is established
|
||||
func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
|
||||
// for heartbeat
|
||||
var heartbeatTimeout bool = false
|
||||
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
|
||||
heartbeatTimeout = true
|
||||
c.Close()
|
||||
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
// ignore response content now
|
||||
content, err := connection.ReadLine()
|
||||
if err == io.EOF || nil == connection || connection.IsClosed() {
|
||||
log.Debug("ProxyName [%s], server close this control conn", cli.Name)
|
||||
var sleepTime time.Duration = 1
|
||||
buf, err := c.ReadLine()
|
||||
if err == io.EOF || c == nil || c.IsClosed() {
|
||||
c.Close()
|
||||
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
|
||||
var delayTime time.Duration = 1
|
||||
|
||||
// loop until connect to server
|
||||
// loop until reconnect to frps
|
||||
for {
|
||||
log.Debug("ProxyName [%s], try to reconnect to server[%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
tmpConn, err := loginToServer(cli)
|
||||
log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
c, err = loginToServer(cli)
|
||||
if err == nil {
|
||||
connection.Close()
|
||||
connection = tmpConn
|
||||
close(msgSendChan)
|
||||
msgSendChan = make(chan interface{}, 1024)
|
||||
go heartbeatSender(c, msgSendChan)
|
||||
go msgSender(cli, c, msgSendChan)
|
||||
break
|
||||
}
|
||||
|
||||
if sleepTime < 60 {
|
||||
sleepTime = sleepTime * 2
|
||||
if delayTime < 60 {
|
||||
delayTime = delayTime * 2
|
||||
}
|
||||
time.Sleep(sleepTime * time.Second)
|
||||
time.Sleep(delayTime * time.Second)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err)
|
||||
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
clientCtlRes := &msg.ClientCtlRes{}
|
||||
if err := json.Unmarshal([]byte(content), clientCtlRes); err != nil {
|
||||
log.Warn("Parse err: %v : %s", err, content)
|
||||
continue
|
||||
}
|
||||
if consts.SCHeartBeatRes == clientCtlRes.GeneralRes.Code {
|
||||
if heartBeatTimer != nil {
|
||||
log.Debug("Client rcv heartbeat response")
|
||||
heartBeatTimer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
|
||||
} else {
|
||||
log.Error("heartBeatTimer is nil")
|
||||
}
|
||||
ctlRes := &msg.ControlRes{}
|
||||
if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
|
||||
log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
|
||||
continue
|
||||
}
|
||||
|
||||
cli.StartTunnel(client.ServerAddr, client.ServerPort)
|
||||
switch ctlRes.Type {
|
||||
case consts.HeartbeatRes:
|
||||
log.Debug("ProxyName [%s], receive heartbeat response", cli.Name)
|
||||
timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
|
||||
case consts.NoticeUserConn:
|
||||
log.Debug("ProxyName [%s], new user connection", cli.Name)
|
||||
cli.StartTunnel(client.ServerAddr, client.ServerPort)
|
||||
default:
|
||||
log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loop for sending messages from channel to frps
|
||||
func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) {
|
||||
for {
|
||||
msg, ok := <-msgSendChan
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(msg)
|
||||
err := c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +135,14 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
req := &msg.ClientCtlReq{
|
||||
Type: consts.CtlConn,
|
||||
ProxyName: cli.Name,
|
||||
Passwd: cli.Passwd,
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewCtlConn,
|
||||
ProxyName: cli.Name,
|
||||
AuthKey: authKey,
|
||||
UseEncryption: cli.UseEncryption,
|
||||
Timestamp: nowTime,
|
||||
}
|
||||
buf, _ := json.Marshal(req)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
@@ -115,53 +158,31 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
|
||||
}
|
||||
log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
|
||||
|
||||
clientCtlRes := &msg.ClientCtlRes{}
|
||||
if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil {
|
||||
ctlRes := &msg.ControlRes{}
|
||||
if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
|
||||
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
if clientCtlRes.Code != 0 {
|
||||
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg)
|
||||
return c, fmt.Errorf("%s", clientCtlRes.Msg)
|
||||
if ctlRes.Code != 0 {
|
||||
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
|
||||
return c, fmt.Errorf("%s", ctlRes.Msg)
|
||||
}
|
||||
|
||||
go startHeartBeat(c)
|
||||
log.Debug("ProxyName [%s], connect to server[%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
|
||||
log.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
return
|
||||
}
|
||||
|
||||
func startHeartBeat(c *conn.Conn) {
|
||||
f := func() {
|
||||
log.Error("HeartBeat timeout!")
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
|
||||
heartbeatReq := &msg.ControlReq{
|
||||
Type: consts.HeartbeatReq,
|
||||
}
|
||||
heartBeatTimer = time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, f)
|
||||
defer heartBeatTimer.Stop()
|
||||
|
||||
clientCtlReq := &msg.ClientCtlReq{
|
||||
Type: consts.CSHeartBeatReq,
|
||||
ProxyName: "",
|
||||
Passwd: "",
|
||||
}
|
||||
request, err := json.Marshal(clientCtlReq)
|
||||
if err != nil {
|
||||
log.Warn("Serialize clientCtlReq err! Err: %v", err)
|
||||
}
|
||||
|
||||
log.Debug("Start to send heartbeat")
|
||||
log.Info("Start to send heartbeat to frps")
|
||||
for {
|
||||
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
|
||||
if c != nil && !c.IsClosed() {
|
||||
log.Debug("Send heartbeat to server")
|
||||
err = c.Write(string(request) + "\n")
|
||||
if err != nil {
|
||||
log.Error("Send hearbeat to server failed! Err:%v", err)
|
||||
continue
|
||||
}
|
||||
msgSendChan <- heartbeatReq
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
@@ -88,7 +88,7 @@ func main() {
|
||||
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
|
||||
var wait sync.WaitGroup
|
||||
|
@@ -25,95 +25,171 @@ import (
|
||||
"frp/models/server"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
func ProcessControlConn(l *conn.Listener) {
|
||||
for {
|
||||
c, err := l.GetConn()
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debug("Get one new conn, %v", c.GetRemoteAddr())
|
||||
log.Debug("Get new connection, %v", c.GetRemoteAddr())
|
||||
go controlWorker(c)
|
||||
}
|
||||
}
|
||||
|
||||
// connection from every client and server
|
||||
func controlWorker(c *conn.Conn) {
|
||||
// the first message is from client to server
|
||||
// if error, close connection
|
||||
res, err := c.ReadLine()
|
||||
// if login message type is NewWorkConn, don't close this connection
|
||||
var closeFlag bool = true
|
||||
var s *server.ProxyServer
|
||||
defer func() {
|
||||
if closeFlag {
|
||||
c.Close()
|
||||
if s != nil {
|
||||
s.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// get login message
|
||||
buf, err := c.ReadLine()
|
||||
if err != nil {
|
||||
log.Warn("Read error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Debug("get: %s", res)
|
||||
log.Debug("Get msg from frpc: %s", buf)
|
||||
|
||||
clientCtlReq := &msg.ClientCtlReq{}
|
||||
clientCtlRes := &msg.ClientCtlRes{}
|
||||
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil {
|
||||
log.Warn("Parse err: %v : %s", err, res)
|
||||
cliReq := &msg.ControlReq{}
|
||||
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
|
||||
log.Warn("Parse msg from frpc error: %v : %s", err, buf)
|
||||
return
|
||||
}
|
||||
|
||||
// check
|
||||
succ, info, needRes := checkProxy(clientCtlReq, c)
|
||||
if !succ {
|
||||
clientCtlRes.Code = 1
|
||||
clientCtlRes.Msg = info
|
||||
// do login when type is NewCtlConn or NewWorkConn
|
||||
ret, info := doLogin(cliReq, c)
|
||||
s, ok := server.ProxyServers[cliReq.ProxyName]
|
||||
if !ok {
|
||||
log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
|
||||
return
|
||||
}
|
||||
|
||||
if needRes {
|
||||
defer c.Close()
|
||||
|
||||
buf, _ := json.Marshal(clientCtlRes)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
// if login type is NewWorkConn, nothing will be send to frpc
|
||||
if cliReq.Type != consts.NewWorkConn {
|
||||
cliRes := &msg.ControlRes{
|
||||
Type: consts.NewCtlConnRes,
|
||||
Code: ret,
|
||||
Msg: info,
|
||||
}
|
||||
byteBuf, _ := json.Marshal(cliRes)
|
||||
err = c.Write(string(byteBuf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("Write error, %v", err)
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
|
||||
time.Sleep(1 * time.Second)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// work conn, just return
|
||||
closeFlag = false
|
||||
return
|
||||
}
|
||||
|
||||
// other messages is from server to client
|
||||
s, ok := server.ProxyServers[clientCtlReq.ProxyName]
|
||||
if !ok {
|
||||
log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName)
|
||||
return
|
||||
}
|
||||
// create a channel for sending messages
|
||||
msgSendChan := make(chan interface{}, 1024)
|
||||
go msgSender(s, c, msgSendChan)
|
||||
go noticeUserConn(s, msgSendChan)
|
||||
|
||||
// read control msg from client
|
||||
go readControlMsgFromClient(s, c)
|
||||
|
||||
serverCtlReq := &msg.ClientCtlReq{}
|
||||
serverCtlReq.Type = consts.WorkConn
|
||||
for {
|
||||
closeFlag := s.WaitUserConn()
|
||||
if closeFlag {
|
||||
log.Debug("ProxyName [%s], goroutine for dealing user conn is closed", s.Name)
|
||||
break
|
||||
}
|
||||
buf, _ := json.Marshal(serverCtlReq)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("ProxyName [%s], write to client to add work conn success", s.Name)
|
||||
}
|
||||
// loop for reading control messages from frpc and deal with different types
|
||||
msgReader(s, c, msgSendChan)
|
||||
|
||||
close(msgSendChan)
|
||||
log.Info("ProxyName [%s], I'm dead!", s.Name)
|
||||
return
|
||||
}
|
||||
|
||||
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) {
|
||||
succ = false
|
||||
needRes = true
|
||||
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
|
||||
func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
|
||||
for {
|
||||
closeFlag := s.WaitUserConn()
|
||||
if closeFlag {
|
||||
log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
|
||||
break
|
||||
}
|
||||
notice := &msg.ControlRes{
|
||||
Type: consts.NoticeUserConn,
|
||||
}
|
||||
msgSendChan <- notice
|
||||
log.Debug("ProxyName [%s], notice client to add work conn", s.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// loop for reading messages from frpc after control connection is established
|
||||
func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error {
|
||||
// for heartbeat
|
||||
var heartbeatTimeout bool = false
|
||||
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
|
||||
heartbeatTimeout = true
|
||||
s.Close()
|
||||
c.Close()
|
||||
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
buf, err := c.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
log.Warn("ProxyName [%s], client is dead!", s.Name)
|
||||
return err
|
||||
} else if c == nil || c.IsClosed() {
|
||||
log.Warn("ProxyName [%s], client connection is closed", s.Name)
|
||||
return err
|
||||
}
|
||||
log.Warn("ProxyName [%s], read error: %v", s.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cliReq := &msg.ControlReq{}
|
||||
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
|
||||
log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf)
|
||||
continue
|
||||
}
|
||||
|
||||
switch cliReq.Type {
|
||||
case consts.HeartbeatReq:
|
||||
log.Debug("ProxyName [%s], get heartbeat", s.Name)
|
||||
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
|
||||
heartbeatRes := &msg.ControlRes{
|
||||
Type: consts.HeartbeatRes,
|
||||
}
|
||||
msgSendChan <- heartbeatRes
|
||||
default:
|
||||
log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loop for sending messages from channel to frpc
|
||||
func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) {
|
||||
for {
|
||||
msg, ok := <-msgSendChan
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(msg)
|
||||
err := c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
|
||||
s.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if success, ret equals 0, otherwise greater than 0
|
||||
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
|
||||
ret = 1
|
||||
// check if proxy name exist
|
||||
s, ok := server.ProxyServers[req.ProxyName]
|
||||
if !ok {
|
||||
@@ -122,97 +198,53 @@ func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, ne
|
||||
return
|
||||
}
|
||||
|
||||
// check password
|
||||
if req.Passwd != s.Passwd {
|
||||
info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName)
|
||||
// check authKey
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
|
||||
// authKey avaiable in 15 minutes
|
||||
if nowTime-req.Timestamp > 15*60 {
|
||||
info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
|
||||
log.Warn(info)
|
||||
return
|
||||
} else if req.AuthKey != authKey {
|
||||
info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
|
||||
log.Warn(info)
|
||||
return
|
||||
}
|
||||
|
||||
// control conn
|
||||
if req.Type == consts.CtlConn {
|
||||
if s.Status != consts.Idle {
|
||||
if req.Type == consts.NewCtlConn {
|
||||
if s.Status == consts.Working {
|
||||
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
|
||||
log.Warn(info)
|
||||
return
|
||||
}
|
||||
|
||||
// start proxy and listen for user conn, no block
|
||||
// set infomations from frpc
|
||||
s.UseEncryption = req.UseEncryption
|
||||
|
||||
// start proxy and listen for user connections, no block
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error())
|
||||
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
|
||||
log.Warn(info)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("ProxyName [%s], start proxy success", req.ProxyName)
|
||||
} else if req.Type == consts.WorkConn {
|
||||
} else if req.Type == consts.NewWorkConn {
|
||||
// work conn
|
||||
needRes = false
|
||||
if s.Status != consts.Working {
|
||||
log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName)
|
||||
log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
|
||||
return
|
||||
}
|
||||
|
||||
s.GetNewCliConn(c)
|
||||
// the connection will close after join over
|
||||
s.RecvNewWorkConn(c)
|
||||
} else {
|
||||
info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type)
|
||||
log.Warn(info)
|
||||
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
|
||||
log.Warn("Unsupport login message type [%d]", req.Type)
|
||||
return
|
||||
}
|
||||
|
||||
succ = true
|
||||
ret = 0
|
||||
return
|
||||
}
|
||||
|
||||
func readControlMsgFromClient(s *server.ProxyServer, c *conn.Conn) {
|
||||
isContinueRead := true
|
||||
f := func() {
|
||||
isContinueRead = false
|
||||
s.Close()
|
||||
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
|
||||
}
|
||||
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, f)
|
||||
defer timer.Stop()
|
||||
|
||||
for isContinueRead {
|
||||
content, err := c.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
log.Warn("ProxyName [%s], client is dead!", s.Name)
|
||||
s.Close()
|
||||
break
|
||||
} else if nil == c || c.IsClosed() {
|
||||
log.Warn("ProxyName [%s], client connection is closed", s.Name)
|
||||
break
|
||||
}
|
||||
|
||||
log.Error("ProxyName [%s], read error: %v", s.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
clientCtlReq := &msg.ClientCtlReq{}
|
||||
if err := json.Unmarshal([]byte(content), clientCtlReq); err != nil {
|
||||
log.Warn("Parse err: %v : %s", err, content)
|
||||
continue
|
||||
}
|
||||
if consts.CSHeartBeatReq == clientCtlReq.Type {
|
||||
log.Debug("ProxyName [%s], get heartbeat", s.Name)
|
||||
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
|
||||
|
||||
clientCtlRes := &msg.ClientCtlRes{}
|
||||
clientCtlRes.GeneralRes.Code = consts.SCHeartBeatRes
|
||||
response, err := json.Marshal(clientCtlRes)
|
||||
if err != nil {
|
||||
log.Warn("Serialize ClientCtlRes err! err: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.Write(string(response) + "\n")
|
||||
if err != nil {
|
||||
log.Error("Send heartbeat response to client failed! Err:%v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/version"
|
||||
"frp/utils/vhost"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -88,12 +90,25 @@ func main() {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error("Create listener error, %v", err)
|
||||
os.Exit(-1)
|
||||
log.Error("Create server listener error, %v", err)
|
||||
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")
|
||||
|
@@ -16,18 +16,23 @@ package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
type ProxyClient struct {
|
||||
Name string
|
||||
Passwd string
|
||||
LocalIp string
|
||||
LocalPort int64
|
||||
Name string
|
||||
AuthToken string
|
||||
LocalIp string
|
||||
LocalPort int64
|
||||
Type string
|
||||
UseEncryption bool
|
||||
}
|
||||
|
||||
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
|
||||
@@ -51,10 +56,13 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
|
||||
return
|
||||
}
|
||||
|
||||
req := &msg.ClientCtlReq{
|
||||
Type: consts.WorkConn,
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewWorkConn,
|
||||
ProxyName: p.Name,
|
||||
Passwd: p.Passwd,
|
||||
AuthKey: authKey,
|
||||
Timestamp: nowTime,
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(req)
|
||||
@@ -79,8 +87,13 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
|
||||
}
|
||||
|
||||
// l means local, r means remote
|
||||
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
|
||||
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
|
||||
remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
|
||||
go conn.Join(localConn, remoteConn)
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(localConn, remoteConn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ var (
|
||||
LogFile string = "console"
|
||||
LogWay string = "console"
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
HeartBeatInterval int64 = 20
|
||||
HeartBeatTimeout int64 = 90
|
||||
)
|
||||
@@ -69,23 +70,37 @@ func LoadConf(confFile string) (err error) {
|
||||
LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
var authToken string
|
||||
tmpStr, ok = conf.Get("common", "auth_token")
|
||||
if ok {
|
||||
authToken = tmpStr
|
||||
} else {
|
||||
return fmt.Errorf("auth_token not found")
|
||||
}
|
||||
|
||||
// proxies
|
||||
for name, section := range conf {
|
||||
if name != "common" {
|
||||
proxyClient := &ProxyClient{}
|
||||
// name
|
||||
proxyClient.Name = name
|
||||
|
||||
proxyClient.Passwd, ok = section["passwd"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name)
|
||||
}
|
||||
// auth_token
|
||||
proxyClient.AuthToken = authToken
|
||||
|
||||
// local_ip
|
||||
proxyClient.LocalIp, ok = section["local_ip"]
|
||||
if !ok {
|
||||
// use 127.0.0.1 as default
|
||||
proxyClient.LocalIp = "127.0.0.1"
|
||||
}
|
||||
|
||||
// local_port
|
||||
portStr, ok := section["local_port"]
|
||||
if ok {
|
||||
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
|
||||
@@ -96,6 +111,23 @@ func LoadConf(confFile string) (err error) {
|
||||
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
|
||||
proxyClient.UseEncryption = false
|
||||
useEncryptionStr, ok := section["use_encryption"]
|
||||
if ok && useEncryptionStr == "true" {
|
||||
proxyClient.UseEncryption = true
|
||||
}
|
||||
|
||||
ProxyClients[proxyClient.Name] = proxyClient
|
||||
}
|
||||
}
|
||||
|
@@ -18,20 +18,15 @@ package consts
|
||||
const (
|
||||
Idle = iota
|
||||
Working
|
||||
Closed
|
||||
)
|
||||
|
||||
// connection type
|
||||
// msg type
|
||||
const (
|
||||
CtlConn = iota
|
||||
WorkConn
|
||||
)
|
||||
|
||||
// msg from client to server
|
||||
const (
|
||||
CSHeartBeatReq = 1
|
||||
)
|
||||
|
||||
// msg from server to client
|
||||
const (
|
||||
SCHeartBeatRes = 100
|
||||
NewCtlConn = iota
|
||||
NewWorkConn
|
||||
NoticeUserConn
|
||||
NewCtlConnRes
|
||||
HeartbeatReq
|
||||
HeartbeatRes
|
||||
)
|
||||
|
@@ -19,16 +19,17 @@ type GeneralRes struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type ClientCtlReq struct {
|
||||
Type int64 `json:"type"`
|
||||
ProxyName string `json:"proxy_name"`
|
||||
Passwd string `json:"passwd"`
|
||||
// messages between control connection of frpc and frps
|
||||
type ControlReq struct {
|
||||
Type int64 `json:"type"`
|
||||
ProxyName string `json:"proxy_name,omitempty"`
|
||||
AuthKey string `json:"auth_key, omitempty"`
|
||||
UseEncryption bool `json:"use_encryption, omitempty"`
|
||||
Timestamp int64 `json:"timestamp, omitempty"`
|
||||
}
|
||||
|
||||
type ClientCtlRes struct {
|
||||
GeneralRes
|
||||
}
|
||||
|
||||
type ServerCtlReq struct {
|
||||
Type int64 `json:"type"`
|
||||
type ControlRes struct {
|
||||
Type int64 `json:"type"`
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
@@ -17,19 +17,26 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
|
||||
"frp/utils/vhost"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
BindAddr string = "0.0.0.0"
|
||||
BindPort int64 = 7000
|
||||
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, do not listen a public port for http
|
||||
LogFile string = "console"
|
||||
LogWay string = "console" // console or file
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
HeartBeatTimeout int64 = 90
|
||||
UserConnTimeout int64 = 10
|
||||
|
||||
VhostMuxer *vhost.HttpMuxer
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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")
|
||||
if ok {
|
||||
LogFile = tmpStr
|
||||
@@ -69,30 +83,61 @@ func LoadConf(confFile string) (err error) {
|
||||
LogLevel = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||
if ok {
|
||||
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
// servers
|
||||
for name, section := range conf {
|
||||
if name != "common" {
|
||||
proxyServer := &ProxyServer{}
|
||||
proxyServer.CustomDomains = make([]string, 0)
|
||||
proxyServer.Name = name
|
||||
|
||||
proxyServer.Passwd, ok = section["passwd"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyServer.Name)
|
||||
}
|
||||
|
||||
proxyServer.BindAddr, ok = section["bind_addr"]
|
||||
if !ok {
|
||||
proxyServer.BindAddr = "0.0.0.0"
|
||||
}
|
||||
|
||||
portStr, ok := section["listen_port"]
|
||||
proxyServer.Type, ok = section["type"]
|
||||
if ok {
|
||||
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
|
||||
if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyServer.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
|
||||
proxyServer.Type = "tcp"
|
||||
}
|
||||
|
||||
proxyServer.AuthToken, ok = section["auth_token"]
|
||||
if !ok {
|
||||
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"]
|
||||
if !ok {
|
||||
proxyServer.BindAddr = "0.0.0.0"
|
||||
}
|
||||
|
||||
portStr, ok := section["listen_port"]
|
||||
if ok {
|
||||
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
|
@@ -24,25 +24,34 @@ import (
|
||||
"frp/utils/log"
|
||||
)
|
||||
|
||||
type ProxyServer struct {
|
||||
Name string
|
||||
Passwd string
|
||||
BindAddr string
|
||||
ListenPort int64
|
||||
Status int64
|
||||
type Listener interface {
|
||||
Accept() (*conn.Conn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
listener *conn.Listener // accept new connection from remote users
|
||||
type ProxyServer struct {
|
||||
Name string
|
||||
AuthToken string
|
||||
Type string
|
||||
BindAddr string
|
||||
ListenPort int64
|
||||
UseEncryption bool
|
||||
CustomDomains []string
|
||||
|
||||
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
|
||||
cliConnChan chan *conn.Conn // get client conns from control goroutine
|
||||
workConnChan chan *conn.Conn // get new work conns from control goroutine
|
||||
userConnList *list.List // store user conns
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Init() {
|
||||
p.Status = consts.Idle
|
||||
p.cliConnChan = make(chan *conn.Conn)
|
||||
p.workConnChan = make(chan *conn.Conn)
|
||||
p.ctlMsgChan = make(chan int64)
|
||||
p.userConnList = list.New()
|
||||
p.listeners = make([]Listener, 0)
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Lock() {
|
||||
@@ -56,60 +65,74 @@ func (p *ProxyServer) Unlock() {
|
||||
// start listening for user conns
|
||||
func (p *ProxyServer) Start() (err error) {
|
||||
p.Init()
|
||||
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort)
|
||||
if err != nil {
|
||||
return err
|
||||
if p.Type == "tcp" {
|
||||
l, err := conn.Listen(p.BindAddr, p.ListenPort)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
// start a goroutine for listener to accept user connection
|
||||
go func() {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := p.listener.GetConn()
|
||||
if err != nil {
|
||||
log.Info("ProxyName [%s], listener is closed", p.Name)
|
||||
return
|
||||
}
|
||||
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
|
||||
|
||||
// insert into list
|
||||
p.Lock()
|
||||
if p.Status != consts.Working {
|
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
|
||||
c.Close()
|
||||
p.Unlock()
|
||||
return
|
||||
}
|
||||
p.userConnList.PushBack(c)
|
||||
p.Unlock()
|
||||
|
||||
// put msg to control conn
|
||||
p.ctlMsgChan <- 1
|
||||
|
||||
// set timeout
|
||||
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
element := p.userConnList.Front()
|
||||
if element == nil {
|
||||
for _, listener := range p.listeners {
|
||||
go func(l Listener) {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Info("ProxyName [%s], listener is closed", p.Name)
|
||||
return
|
||||
}
|
||||
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
|
||||
|
||||
userConn := element.Value.(*conn.Conn)
|
||||
if userConn == c {
|
||||
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
|
||||
// insert into list
|
||||
p.Lock()
|
||||
if p.Status != consts.Working {
|
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
|
||||
c.Close()
|
||||
p.Unlock()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
p.userConnList.PushBack(c)
|
||||
p.Unlock()
|
||||
|
||||
// start another goroutine for join two conns from client and user
|
||||
// put msg to control conn
|
||||
p.ctlMsgChan <- 1
|
||||
|
||||
// set timeout
|
||||
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
|
||||
p.Lock()
|
||||
element := p.userConnList.Front()
|
||||
p.Unlock()
|
||||
if element == nil {
|
||||
return
|
||||
}
|
||||
|
||||
userConn := element.Value.(*conn.Conn)
|
||||
if userConn == c {
|
||||
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
|
||||
userConn.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}(listener)
|
||||
}
|
||||
|
||||
// start another goroutine for join two conns from frpc and user
|
||||
go func() {
|
||||
for {
|
||||
cliConn, ok := <-p.cliConnChan
|
||||
workConn, ok := <-p.workConnChan
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -122,7 +145,7 @@ func (p *ProxyServer) Start() (err error) {
|
||||
userConn = element.Value.(*conn.Conn)
|
||||
p.userConnList.Remove(element)
|
||||
} else {
|
||||
cliConn.Close()
|
||||
workConn.Close()
|
||||
p.Unlock()
|
||||
continue
|
||||
}
|
||||
@@ -130,9 +153,14 @@ func (p *ProxyServer) Start() (err error) {
|
||||
|
||||
// msg will transfer to another without modifying
|
||||
// l means local, r means remote
|
||||
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(),
|
||||
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
|
||||
userConn.GetLocalAddr(), userConn.GetRemoteAddr())
|
||||
go conn.Join(cliConn, userConn)
|
||||
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(userConn, workConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(userConn, workConn)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -141,13 +169,19 @@ func (p *ProxyServer) Start() (err error) {
|
||||
|
||||
func (p *ProxyServer) Close() {
|
||||
p.Lock()
|
||||
p.Status = consts.Idle
|
||||
if p.listener != nil {
|
||||
p.listener.Close()
|
||||
if p.Status != consts.Closed {
|
||||
p.Status = consts.Closed
|
||||
if len(p.listeners) != 0 {
|
||||
for _, l := range p.listeners {
|
||||
if l != nil {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
close(p.ctlMsgChan)
|
||||
close(p.workConnChan)
|
||||
p.userConnList = list.New()
|
||||
}
|
||||
close(p.ctlMsgChan)
|
||||
close(p.cliConnChan)
|
||||
p.userConnList = list.New()
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
@@ -161,6 +195,6 @@ func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProxyServer) GetNewCliConn(c *conn.Conn) {
|
||||
p.cliConnChan <- c
|
||||
func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
|
||||
p.workConnChan <- c
|
||||
}
|
||||
|
@@ -20,14 +20,16 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
addr net.Addr
|
||||
l *net.TCPListener
|
||||
conns chan *Conn
|
||||
accept chan *Conn
|
||||
closeFlag bool
|
||||
}
|
||||
|
||||
@@ -41,7 +43,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
|
||||
l = &Listener{
|
||||
addr: listener.Addr(),
|
||||
l: listener,
|
||||
conns: make(chan *Conn),
|
||||
accept: make(chan *Conn),
|
||||
closeFlag: false,
|
||||
}
|
||||
|
||||
@@ -60,7 +62,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
|
||||
closeFlag: false,
|
||||
}
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
l.conns <- c
|
||||
l.accept <- c
|
||||
}
|
||||
}()
|
||||
return l, err
|
||||
@@ -68,30 +70,38 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
|
||||
|
||||
// wait util get one new connection or listener is closed
|
||||
// if listener is closed, err returned
|
||||
func (l *Listener) GetConn() (conn *Conn, err error) {
|
||||
var ok bool
|
||||
conn, ok = <-l.conns
|
||||
func (l *Listener) Accept() (*Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return conn, fmt.Errorf("channel close")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() {
|
||||
func (l *Listener) Close() error {
|
||||
if l.l != nil && l.closeFlag == false {
|
||||
l.closeFlag = true
|
||||
l.l.Close()
|
||||
close(l.conns)
|
||||
close(l.accept)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrap for TCPConn
|
||||
type Conn struct {
|
||||
TcpConn *net.TCPConn
|
||||
TcpConn net.Conn
|
||||
Reader *bufio.Reader
|
||||
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) {
|
||||
c = &Conn{}
|
||||
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
|
||||
@@ -127,6 +137,12 @@ func (c *Conn) ReadLine() (buff string, err error) {
|
||||
func (c *Conn) Write(content string) (err error) {
|
||||
_, err = c.TcpConn.Write([]byte(content))
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *Conn) SetDeadline(t time.Time) error {
|
||||
err := c.TcpConn.SetDeadline(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
@@ -151,7 +167,7 @@ func Join(c1 *Conn, c2 *Conn) {
|
||||
var err error
|
||||
_, err = io.Copy(to.TcpConn, from.TcpConn)
|
||||
if err != nil {
|
||||
log.Warn("join conns error, %v", err)
|
||||
log.Warn("join connections error, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,3 +177,93 @@ func Join(c1 *Conn, c2 *Conn) {
|
||||
wait.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// messages from c1 to c2 will be encrypted
|
||||
// and from c2 to c1 will be decrypted
|
||||
func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
|
||||
var wait sync.WaitGroup
|
||||
encryptPipe := func(from *Conn, to *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeEncrypt(from.TcpConn, to.TcpConn, key)
|
||||
}
|
||||
|
||||
decryptPipe := func(to *Conn, from *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeDecrypt(to.TcpConn, from.TcpConn, key)
|
||||
}
|
||||
|
||||
wait.Add(2)
|
||||
go encryptPipe(c1, c2, cryptKey)
|
||||
go decryptPipe(c2, c1, cryptKey)
|
||||
wait.Wait()
|
||||
log.Debug("One tunnel stopped")
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt msg from reader, then write into writer
|
||||
func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
for {
|
||||
buf, err := nreader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := laes.Decrypt(buf)
|
||||
if err != nil {
|
||||
log.Error("Decrypt [%s] error, %v", string(buf), err)
|
||||
return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
|
||||
}
|
||||
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recvive msg from reader, then encrypt msg into write
|
||||
func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
buf := make([]byte, 10*1024)
|
||||
|
||||
for {
|
||||
n, err := nreader.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := laes.Encrypt(buf[:n])
|
||||
if err != nil {
|
||||
log.Error("Encrypt error: %v", err)
|
||||
return fmt.Errorf("Encrypt error: %v", err)
|
||||
}
|
||||
|
||||
res = append(res, '\n')
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
@@ -26,24 +27,24 @@ func init() {
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
|
||||
func InitLog(logWay string, logFile string, logLevel string) {
|
||||
SetLogFile(logWay, logFile)
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
||||
SetLogFile(logWay, logFile, maxdays)
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// logWay: such as file or console
|
||||
func SetLogFile(logWay string, logFile string) {
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
if logWay == "console" {
|
||||
Log.SetLogger("console", "")
|
||||
} else {
|
||||
Log.SetLogger("file", `{"filename": "`+logFile+`"}`)
|
||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||
Log.SetLogger("file", params)
|
||||
}
|
||||
}
|
||||
|
||||
// value: error, warning, info, debug
|
||||
func SetLogLevel(logLevel string) {
|
||||
level := 4 // warning
|
||||
|
||||
switch logLevel {
|
||||
case "error":
|
||||
level = 3
|
||||
@@ -56,7 +57,6 @@ func SetLogLevel(logLevel string) {
|
||||
default:
|
||||
level = 4
|
||||
}
|
||||
|
||||
Log.SetLevel(level)
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"compress/gzip"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
@@ -33,43 +34,40 @@ type Pcrypto struct {
|
||||
|
||||
func (pc *Pcrypto) Init(key []byte) error {
|
||||
var err error
|
||||
pc.pkey = PKCS7Padding(key, aes.BlockSize)
|
||||
pc.pkey = pKCS7Padding(key, aes.BlockSize)
|
||||
pc.paes, err = aes.NewCipher(pc.pkey)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Encrypto(src []byte) ([]byte, error) {
|
||||
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
|
||||
// gzip
|
||||
var zbuf bytes.Buffer
|
||||
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zwr.Close()
|
||||
zwr.Write(src)
|
||||
zwr.Flush()
|
||||
|
||||
// aes
|
||||
src = PKCS7Padding(src, aes.BlockSize)
|
||||
src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
|
||||
crypted := make([]byte, len(src))
|
||||
blockMode.CryptBlocks(crypted, src)
|
||||
|
||||
// gzip
|
||||
var zbuf bytes.Buffer
|
||||
zwr := gzip.NewWriter(&zbuf)
|
||||
defer zwr.Close()
|
||||
zwr.Write(crypted)
|
||||
zwr.Flush()
|
||||
|
||||
// base64
|
||||
return []byte(base64.StdEncoding.EncodeToString(zbuf.Bytes())), nil
|
||||
return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
|
||||
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
|
||||
// base64
|
||||
data, err := base64.StdEncoding.DecodeString(string(str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// gunzip
|
||||
zbuf := bytes.NewBuffer(data)
|
||||
zrd, _ := gzip.NewReader(zbuf)
|
||||
defer zrd.Close()
|
||||
data, _ = ioutil.ReadAll(zrd)
|
||||
|
||||
// aes
|
||||
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
|
||||
if err != nil {
|
||||
@@ -83,19 +81,35 @@ func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
|
||||
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
|
||||
|
||||
blockMode.CryptBlocks(decryptText, decryptText)
|
||||
decryptText = PKCS7UnPadding(decryptText)
|
||||
decryptText = pKCS7UnPadding(decryptText)
|
||||
|
||||
return decryptText, nil
|
||||
// gunzip
|
||||
zbuf := bytes.NewBuffer(decryptText)
|
||||
zrd, err := gzip.NewReader(zbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zrd.Close()
|
||||
data, _ = ioutil.ReadAll(zrd)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS7UnPadding(origData []byte) []byte {
|
||||
func pKCS7UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
func GetAuthKey(str string) (authKey string) {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(str))
|
||||
md5Str := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(md5Str)
|
||||
}
|
||||
|
@@ -15,15 +15,14 @@
|
||||
package pcrypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncrypto(t *testing.T) {
|
||||
func TestEncrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypto([]byte("Just One Test!"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -31,31 +30,18 @@ func TestEncrypto(t *testing.T) {
|
||||
fmt.Printf("[%x]\n", res)
|
||||
}
|
||||
|
||||
func TestDecrypto(t *testing.T) {
|
||||
func TestDecrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypto([]byte("Just One Test!"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err = pp.Decrypto(res)
|
||||
res, err = pp.Decrypt(res)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s]\n", string(res))
|
||||
}
|
||||
|
||||
func TestPKCS7Padding(t *testing.T) {
|
||||
ltt := []byte("Test_PKCS7Padding")
|
||||
ltt = PKCS7Padding(ltt, aes.BlockSize)
|
||||
// fmt.Printf("[%x]\n", (ltt))
|
||||
}
|
||||
|
||||
func TestPKCS7UnPadding(t *testing.T) {
|
||||
ltt := []byte("Test_PKCS7Padding")
|
||||
ltt = PKCS7Padding(ltt, aes.BlockSize)
|
||||
ltt = PKCS7UnPadding(ltt)
|
||||
// fmt.Printf("[%x]\n", ltt)
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.2.0"
|
||||
var version string = "0.5.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
193
src/frp/utils/vhost/vhost.go
Normal file
193
src/frp/utils/vhost/vhost.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user