Compare commits

..

56 Commits

Author SHA1 Message Date
fatedier
14733dd109 Merge pull request #608 from fatedier/dev
bump version to 0.15.0
2018-01-18 16:49:55 +08:00
fatedier
74b75e8c57 Merge pull request #607 from fatedier/doc
doc: update
2018-01-18 16:46:33 +08:00
fatedier
63e6e0dc92 doc: update 2018-01-18 16:43:03 +08:00
fatedier
4d4a738aa9 web: update assets 2018-01-18 15:26:30 +08:00
fatedier
1ed130e704 version: to v0.15.0 2018-01-18 15:08:16 +08:00
fatedier
2e773d550b Merge pull request #606 from fatedier/test
tests: more ci case
2018-01-18 15:04:03 +08:00
fatedier
e155ff056e tests: more ci case 2018-01-18 14:53:44 +08:00
fatedier
37210d9983 Merge branch 'dev' of github.com:fatedier/frp into dev 2018-01-18 00:46:21 +08:00
fatedier
338d5bae37 fix panic when using socks5 plugin with encryption and compression, fix #446 2018-01-18 00:45:11 +08:00
fatedier
3e62198612 Merge pull request #604 from fatedier/http
fix new http no traffic stats, fix #590
2018-01-17 23:28:48 +08:00
fatedier
4f7dfcdb31 fix new http no traffic stats, fix #590 2018-01-17 23:17:15 +08:00
fatedier
5b08201e5d Merge pull request #603 from fatedier/test
add test cases and new feature assgin a random port if remote_port is 0
2018-01-17 22:45:02 +08:00
fatedier
b2c846664d new feature: assign a random port if remote_port is 0 in type tcp and
udp
2018-01-17 22:18:34 +08:00
fatedier
3f6799c06a add remoteAddr in NewProxyResp message 2018-01-17 15:01:26 +08:00
fatedier
9a5f0c23c4 fix ci 2018-01-17 01:18:40 +08:00
fatedier
afde0c515c packages: add package github.com/rodaine/table 2018-01-17 01:15:34 +08:00
fatedier
584e098e8e frpc: add status command 2018-01-17 01:09:33 +08:00
fatedier
37395b3ef5 Merge pull request #596 from NemoAlex/patch-2
Use sans-serif font in web
2018-01-10 10:36:28 +08:00
NemoAlex
43fb3f3ff7 Use sans-serif font in web 2018-01-08 13:56:51 +08:00
fatedier
82b127494c Merge pull request #576 from gtt116/master
Close connection if frpc can't connection to local server
2017-12-26 14:51:02 +08:00
gtt116
4d79648657 Close connection if frpc can't connection to local server
Now, when frpc can't connect to local server it leaves the connection alone, the patch fix it.

Fixed #575
2017-12-26 14:39:07 +08:00
fatedier
3bb404dfb5 more test case 2017-12-18 19:35:09 +08:00
fatedier
ff4bdec3f7 add test case 2017-12-16 23:59:46 +08:00
fatedier
69f8b08ac0 update role error log info 2017-12-16 21:56:13 +08:00
fatedier
d873df5ca8 let role default value to 'server' 2017-12-15 11:40:08 +08:00
fatedier
a384bf5580 Merge pull request #564 from fatedier/dev
bump version to v0.14.1
2017-12-14 22:28:25 +08:00
fatedier
92046a7ca2 Merge pull request #561 from fatedier/http
improve http vhost package
2017-12-13 23:48:18 +08:00
fatedier
4cc5ddc012 newhttp support BasicAuth 2017-12-13 23:44:27 +08:00
fatedier
46358d466d support encryption and compression in new http reverser proxy 2017-12-13 04:28:58 +08:00
fatedier
7da61f004b improve http vhost package 2017-12-13 03:27:43 +08:00
fatedier
63037f1c65 typo fix 2017-12-11 22:46:45 +08:00
fatedier
cc160995da improve error role log info 2017-12-11 16:21:17 +08:00
fatedier
de48d97cb2 fix kcp port print error 2017-12-11 01:36:47 +08:00
fatedier
1a6a179b68 visitor: fix panic 2017-12-05 22:26:53 +08:00
fatedier
3a2946a2ff Merge pull request #549 from fatedier/dev
bump version to v0.14.0
2017-12-05 01:42:00 +08:00
fatedier
ae9a4623d9 Merge pull request #548 from fatedier/doc
update doc and fix vistor -> visitor
2017-12-05 01:38:03 +08:00
fatedier
bd1e9a3010 update doc and fix vistor -> visitor 2017-12-05 01:34:33 +08:00
fatedier
92fff5c191 Merge pull request #539 from timerever/dev
add custom dashboard bind address
2017-11-29 10:34:36 +08:00
timerever
8c65b337ca add custom dashboard bind address 2017-11-28 15:56:34 +08:00
fatedier
0f1005ff61 using glide 2017-11-01 16:21:57 +08:00
fatedier
ad858a0d32 prevent sending on a closed channel in vhost package, fix #502 2017-11-01 10:51:30 +08:00
fatedier
1e905839f0 update kcp connection options 2017-10-25 03:02:25 +08:00
fatedier
bf50f932d9 update version to v0.14.0 2017-10-25 02:55:36 +08:00
fatedier
673047be2c Merge pull request #496 from fatedier/0.14
xtcp for p2p communication
2017-10-24 13:54:32 -05:00
fatedier
fa2b9a836c fix xtcp encryption 2017-10-25 02:49:56 +08:00
fatedier
9e0fd0c4ef add packages 2017-10-25 02:29:04 +08:00
fatedier
0559865fe5 support xtcp for making nat hole 2017-10-25 01:27:04 +08:00
fatedier
4fc85a36c2 Merge pull request #486 from xiaox0321/patch-1
Update version.go
2017-10-20 04:12:47 -05:00
xiaox0321
3f1174a519 Update version.go
Optimize duplicate code
2017-10-20 15:58:03 +08:00
fatedier
bcbdfcb99b Merge pull request #473 from Hyduan/master
doc: fix spelling error
2017-09-27 11:55:17 -05:00
Hyduan
df046bdeeb doc: fix spelling error 2017-09-26 21:06:28 +08:00
fatedier
f83447c652 Merge pull request #461 from dvrkps/patch-1
travis: add 1.x to go versions
2017-09-11 06:04:14 -05:00
Davor Kapsa
9ae69b4aac travis: add 1.x to go versions 2017-09-11 12:41:33 +02:00
fatedier
c48a89731a Merge pull request #454 from GeorgeYuen/diamondyuan
add Dockerfile_multiple_build
2017-09-06 01:07:36 -05:00
袁凡迪
36b58ab60c add Dockerfile_multiple_build 2017-09-06 12:51:29 +08:00
fatedier
6320f15a7c typo for default config file name used for frpc 2017-07-19 22:56:12 +08:00
1193 changed files with 304476 additions and 1163 deletions

View File

@@ -3,6 +3,7 @@ language: go
go:
- 1.8.x
- 1.9.x
install:
- make

21
Dockerfile_multiple_build Normal file
View File

@@ -0,0 +1,21 @@
FROM golang:1.8 as frpBuild
COPY . /go/src/github.com/fatedier/frp
ENV CGO_ENABLED=0
RUN cd /go/src/github.com/fatedier/frp \
&& make
FROM alpine:3.6
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frpc /
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frpc.ini /
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frps /
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frps.ini /
EXPOSE 80 443 6000 7000 7500
WORKDIR /
CMD ["/frps","-c","frps.ini"]

134
Godeps/Godeps.json generated
View File

@@ -1,134 +0,0 @@
{
"ImportPath": "github.com/fatedier/frp",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/armon/go-socks5",
"Rev": "e75332964ef517daa070d7c38a9466a0d687e0a5"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
},
{
"ImportPath": "github.com/docopt/docopt-go",
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/fatedier/beego/logs",
"Comment": "v1.7.2-72-gf73c369",
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
},
{
"ImportPath": "github.com/julienschmidt/httprouter",
"Comment": "v1.1-41-g8a45e95",
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
},
{
"ImportPath": "github.com/klauspost/cpuid",
"Comment": "v1.0",
"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
},
{
"ImportPath": "github.com/klauspost/reedsolomon",
"Comment": "1.3-1-gdde6ad5",
"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
},
{
"ImportPath": "github.com/pkg/errors",
"Comment": "v0.8.0-5-gc605e28",
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Comment": "v1.0.0",
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
},
{
"ImportPath": "github.com/rakyll/statik/fs",
"Comment": "v0.1.0",
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Comment": "v1.1.4-25-g2402e8e",
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "github.com/xtaci/kcp-go",
"Comment": "v3.17",
"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
},
{
"ImportPath": "github.com/xtaci/smux",
"Comment": "v1.0.5-8-g2de5471",
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/cast5",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/salsa20",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/tea",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/twofish",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/crypto/xtea",
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
},
{
"ImportPath": "golang.org/x/net/bpf",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/internal/socket",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/ipv4",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
}
]
}

View File

@@ -39,11 +39,13 @@ gotest:
go test -v ./server/...
go test -v ./utils/...
alltest: gotest
ci:
cd ./tests && ./run_test.sh && cd -
go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd -
alltest: gotest ci
clean:
rm -f ./bin/frpc
rm -f ./bin/frps

View File

@@ -11,6 +11,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
## Table of Contents
<!-- vim-markdown-toc GFM -->
* [What can I do with frp?](#what-can-i-do-with-frp)
* [Status](#status)
* [Architecture](#architecture)
@@ -20,6 +21,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Forward DNS query request](#forward-dns-query-request)
* [Forward unix domain socket](#forward-unix-domain-socket)
* [Expose your service in security](#expose-your-service-in-security)
* [P2P Mode](#p2p-mode)
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
* [Features](#features)
* [Configuration File](#configuration-file)
@@ -27,6 +29,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Get proxy status from client](#get-proxy-status-from-client)
* [Privilege Mode](#privilege-mode)
* [Port White List](#port-white-list)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
@@ -184,7 +187,7 @@ However, we can expose a http or https service using frp.
5. Send dns query request by dig:
`dig @x.x.x.x -p 6000 www.goolge.com`
`dig @x.x.x.x -p 6000 www.google.com`
### Forward unix domain socket
@@ -242,9 +245,9 @@ Configure frps same as above.
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
[secret_ssh_visitor]
type = stcp
role = vistor
role = visitor
server_name = secret_ssh
sk = abcdefg
bind_addr = 127.0.0.1
@@ -255,6 +258,54 @@ Configure frps same as above.
`ssh -oPort=6000 test@127.0.0.1`
### P2P Mode
**xtcp** is designed for transmitting a large amount of data directly between two client.
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
1. Configure a udp port for xtcp:
```ini
bind_udp_port = 7001
```
2. Start frpc, forward ssh port and `remote_port` is useless:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh]
type = xtcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
3. Start another frpc in which you want to connect this ssh server:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh_visitor]
type = xtcp
role = visitor
server_name = p2p_ssh
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 6000
```
4. Connect to server in LAN by ssh assuming that username is test:
`ssh -oPort=6000 test@127.0.0.1`
### Connect website through frpc's network
Configure frps same as above.
@@ -336,10 +387,14 @@ admin_addr = 127.0.0.1
admin_port = 7400
```
Then run command `frpc -c ./frpc.ini --reload` and wait for about 10 seconds to let frpc create or update or delete proxies.
Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
**Note that parameters in [common] section won't be modified except 'start' now.**
### Get proxy status from client
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
### Privilege Mode
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client.
@@ -550,7 +605,7 @@ plugin_http_passwd = abc
* Direct reverse proxy, like haproxy.
* Load balance to different service in frpc.
* Frpc can directly be a webserver for static files.
* P2p communicate by make udp hole to penetrate NAT.
* P2p communicate by making udp hole to penetrate NAT.
* kubernetes ingress support.

View File

@@ -9,6 +9,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
## 目录
<!-- vim-markdown-toc GFM -->
* [frp 的作用](#frp-的作用)
* [开发状态](#开发状态)
* [架构](#架构)
@@ -18,6 +19,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字)
* [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
* [功能说明](#功能说明)
* [配置文件](#配置文件)
@@ -25,6 +27,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [身份验证](#身份验证)
* [加密与压缩](#加密与压缩)
* [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态)
* [特权模式](#特权模式)
* [端口白名单](#端口白名单)
* [TCP 多路复用](#tcp-多路复用)
@@ -185,7 +188,7 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
`dig @x.x.x.x -p 6000 www.goolge.com`
`dig @x.x.x.x -p 6000 www.google.com`
### 转发 Unix域套接字
@@ -246,10 +249,10 @@ frps 的部署步骤同上。
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
[secret_ssh_visitor]
type = stcp
# stcp 的访问者
role = vistor
role = visitor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
sk = abcdefg
@@ -262,6 +265,60 @@ frps 的部署步骤同上。
`ssh -oPort=6000 test@127.0.0.1`
### 点对点内网穿透
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
```ini
bind_udp_port = 7001
```
2. 启动 frpc转发内网的 ssh 服务,配置如下,不需要指定远程端口:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh]
type = xtcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
3. 在要访问这个服务的机器上启动另外一个 frpc配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[p2p_ssh_visitor]
type = xtcp
# xtcp 的访问者
role = visitor
# 要访问的 xtcp 代理的名字
server_name = p2p_ssh
sk = abcdefg
# 绑定本地端口用于访问 ssh 服务
bind_addr = 127.0.0.1
bind_port = 6000
```
4. 通过 ssh 访问内网机器,假设用户名为 test:
`ssh -oPort=6000 test@127.0.0.1`
### 通过 frpc 所在机器访问外网
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
@@ -340,7 +397,7 @@ use_compression = true
### 客户端热加载配置文件
当修改了 frpc 中的代理配置,可以通过 `frpc --reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
@@ -353,12 +410,16 @@ admin_port = 7400
之后执行重启命令:
`frpc -c ./frpc.ini --reload`
`frpc reload -c ./frpc.ini`
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
### 客户端查看代理状态
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
### 特权模式
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。

View File

@@ -1 +1 @@
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5217927b66cc446ebfd3"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?bf962cded96400bef9a0"></script></body> </html>
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?facf06d98c7e1aea259d"></script><script type="text/javascript" src="vendor.js?a05a344be2b42183469b"></script><script type="text/javascript" src="index.js?a914c2dc7a5bb16ad443"></script></body> </html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"bf962cded96400bef9a0",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,a,c){for(var u,i,f,l=0,s=[];l<t.length;l++)i=t[l],o[i]&&s.push(o[i][0]),o[i]=0;for(u in a)Object.prototype.hasOwnProperty.call(a,u)&&(e[u]=a[u]);for(r&&r(t,a,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(i);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var a=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=a;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+""+e+".js?"+{0:"a914c2dc7a5bb16ad443",1:"a05a344be2b42183469b"}[e];var i=setTimeout(r,12e4);return u.onerror=u.onload=r,c.appendChild(u),a},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -31,7 +31,7 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// url router
router := httprouter.New()
@@ -39,6 +39,7 @@ func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
// api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
router.GET("/api/status", frpNet.HttprouterBasicAuth(svr.apiStatus, user, passwd))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{

View File

@@ -16,7 +16,10 @@ package client
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini"
@@ -64,7 +67,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return
}
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
if err != nil {
res.Code = 3
res.Msg = err.Error()
@@ -72,7 +75,137 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return
}
svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
err = svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
if err != nil {
res.Code = 4
res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err)
return
}
log.Info("success reload conf")
return
}
type StatusResp struct {
Tcp []ProxyStatusResp `json:"tcp"`
Udp []ProxyStatusResp `json:"udp"`
Http []ProxyStatusResp `json:"http"`
Https []ProxyStatusResp `json:"https"`
Stcp []ProxyStatusResp `json:"stcp"`
Xtcp []ProxyStatusResp `json:"xtcp"`
}
type ProxyStatusResp struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Err string `json:"err"`
LocalAddr string `json:"local_addr"`
Plugin string `json:"plugin"`
RemoteAddr string `json:"remote_addr"`
}
type ByProxyStatusResp []ProxyStatusResp
func (a ByProxyStatusResp) Len() int { return len(a) }
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
psr := ProxyStatusResp{
Name: status.Name,
Type: status.Type,
Status: status.Status,
Err: status.Err,
}
switch cfg := status.Cfg.(type) {
case *config.TcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr
}
case *config.UdpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr
}
case *config.HttpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.HttpsProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.StcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
case *config.XtcpProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
}
return psr
}
// api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res StatusResp
)
res.Tcp = make([]ProxyStatusResp, 0)
res.Udp = make([]ProxyStatusResp, 0)
res.Http = make([]ProxyStatusResp, 0)
res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0)
defer func() {
log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/status]")
ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps {
switch status.Type {
case "tcp":
res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
case "udp":
res.Udp = append(res.Udp, NewProxyStatusResp(status))
case "http":
res.Http = append(res.Http, NewProxyStatusResp(status))
case "https":
res.Https = append(res.Https, NewProxyStatusResp(status))
case "stcp":
res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
case "xtcp":
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
}
}
sort.Sort(ByProxyStatusResp(res.Tcp))
sort.Sort(ByProxyStatusResp(res.Udp))
sort.Sort(ByProxyStatusResp(res.Http))
sort.Sort(ByProxyStatusResp(res.Https))
sort.Sort(ByProxyStatusResp(res.Stcp))
sort.Sort(ByProxyStatusResp(res.Xtcp))
return
}

View File

@@ -24,9 +24,9 @@ import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/xtaci/smux"
@@ -40,20 +40,10 @@ type Control struct {
// frpc service
svr *Service
// login message to server
// login message to server, only used
loginMsg *msg.Login
// proxy configures
pxyCfgs map[string]config.ProxyConf
// proxies
proxies map[string]Proxy
// vistor configures
vistorCfgs map[string]config.ProxyConf
// vistors
vistors map[string]Vistor
pm *ProxyManager
// control connection
conn frpNet.Conn
@@ -79,12 +69,16 @@ type Control struct {
// last time got the Pong message
lastPong time.Time
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
msgHandlerShutdown *shutdown.Shutdown
mu sync.RWMutex
log.Logger
}
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) *Control {
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
@@ -92,28 +86,22 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs ma
User: config.ClientCommonCfg.User,
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
vistorCfgs: vistorCfgs,
proxies: make(map[string]Proxy),
vistors: make(map[string]Vistor),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
ctl := &Control{
svr: svr,
loginMsg: loginMsg,
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
msgHandlerShutdown: shutdown.New(),
Logger: log.NewPrefixLogger(""),
}
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
ctl.pm.Reload(pxyCfgs, visitorCfgs)
return ctl
}
// 1. login
// 2. start reader() writer() manager()
// 3. connection closed
// 4. In reader(): close closedCh and exit, controler() get it
// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
// 6. In controler(): ini readCh, sendCh, closedCh
// 7. In controler(): start new reader(), writer(), manager()
// controler() will keep running
func (ctl *Control) Run() (err error) {
for {
err = ctl.login()
@@ -125,47 +113,29 @@ func (ctl *Control) Run() (err error) {
if config.ClientCommonCfg.LoginFailExit {
return
} else {
time.Sleep(30 * time.Second)
time.Sleep(10 * time.Second)
}
} else {
break
}
}
go ctl.controler()
go ctl.manager()
go ctl.writer()
go ctl.reader()
go ctl.worker()
// start all local vistors
for _, cfg := range ctl.vistorCfgs {
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
// start all local visitors and send NewProxy message for all configured proxies
ctl.pm.Reset(ctl.sendCh, ctl.runId)
ctl.pm.CheckAndStartProxy()
return nil
}
func (ctl *Control) NewWorkConn() {
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
workConn, err := ctl.connectServer()
if err != nil {
return
}
m := &msg.NewWorkConn{
RunId: ctl.getRunId(),
RunId: ctl.runId,
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
@@ -182,33 +152,26 @@ func (ctl *Control) NewWorkConn() {
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
pxy, ok := ctl.getProxy(startMsg.ProxyName)
if ok {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
}
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
if err != nil {
ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
} else {
workConn.Close()
ctl.Info("[%s] start proxy success", inMsg.ProxyName)
}
}
func (ctl *Control) Close() error {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.exit = true
err := errors.PanicToError(func() {
for name, _ := range ctl.proxies {
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
})
ctl.mu.Unlock()
return err
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
ctl.pm.CloseProxies()
return nil
}
// login send a login message to server and wait for a loginResp message.
@@ -249,7 +212,7 @@ func (ctl *Control) login() (err error) {
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.getRunId()
ctl.loginMsg.RunId = ctl.runId
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
@@ -270,15 +233,11 @@ func (ctl *Control) login() (err error) {
ctl.conn = conn
// update runId got from server
ctl.setRunId(loginRespMsg.RunId)
ctl.runId = loginRespMsg.RunId
config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
// login success, so we let closedCh available again
ctl.closedCh = make(chan int)
ctl.lastPong = time.Now()
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
return nil
}
@@ -291,7 +250,6 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
return
}
conn = frpNet.WrapConn(stream)
} else {
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
@@ -303,12 +261,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
return
}
// reader read all messages from frps and send to readCh
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
defer ctl.readerShutdown.Done()
defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
@@ -327,7 +287,9 @@ func (ctl *Control) reader() {
}
}
// writer writes messages got from sendCh to frps
func (ctl *Control) writer() {
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
@@ -347,19 +309,22 @@ func (ctl *Control) writer() {
}
}
// manager handles all channel events and do corresponding process
func (ctl *Control) manager() {
// msgHandler handles all channel events and do corresponding operations.
func (ctl *Control) msgHandler() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
defer ctl.msgHandlerShutdown.Done()
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
ctl.lastPong = time.Now()
for {
select {
case <-hbSend.C:
@@ -380,35 +345,9 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.ReqWorkConn:
go ctl.NewWorkConn()
go ctl.HandleReqWorkConn(m)
case *msg.NewProxyResp:
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
if m.Error != "" {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
cfg, ok := ctl.getProxyConf(m.ProxyName)
if !ok {
// it will never go to this branch now
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
continue
}
oldPxy, ok := ctl.getProxy(m.ProxyName)
if ok {
oldPxy.Close()
}
pxy := NewProxy(ctl, cfg)
if err := pxy.Run(); err != nil {
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: m.ProxyName,
}
continue
}
ctl.addProxy(m.ProxyName, pxy)
ctl.Info("[%s] start proxy success", m.ProxyName)
ctl.HandleNewProxyResp(m)
case *msg.Pong:
ctl.lastPong = time.Now()
ctl.Debug("receive heartbeat from server")
@@ -418,10 +357,14 @@ func (ctl *Control) manager() {
}
// controler keep watching closedCh, start a new connection if previous control connection is closed.
// If controler is notified by closedCh, reader and writer and manager will exit, then recall these functions.
func (ctl *Control) controler() {
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
func (ctl *Control) worker() {
go ctl.msgHandler()
go ctl.writer()
go ctl.reader()
var err error
maxDelayTime := 30 * time.Second
maxDelayTime := 20 * time.Second
delayTime := time.Second
checkInterval := 10 * time.Second
@@ -429,41 +372,20 @@ func (ctl *Control) controler() {
for {
select {
case <-checkProxyTicker.C:
// Every 10 seconds, check which proxy registered failed and reregister it to server.
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to register proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
for _, cfg := range ctl.vistorCfgs {
if _, exist := ctl.vistors[cfg.GetName()]; !exist {
ctl.Info("try to start vistor [%s]", cfg.GetName())
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
}
ctl.mu.RUnlock()
// every 10 seconds, check which proxy registered failed and reregister it to server
ctl.pm.CheckAndStartProxy()
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
// close related channels
// close related channels and wait until other goroutines done
close(ctl.readCh)
close(ctl.sendCh)
ctl.readerShutdown.WaitDone()
ctl.msgHandlerShutdown.WaitDone()
for _, pxy := range ctl.proxies {
pxy.Close()
}
close(ctl.sendCh)
ctl.writerShutdown.WaitDone()
ctl.pm.CloseProxies()
// if ctl.exit is true, just exit
ctl.mu.RLock()
exit := ctl.exit
@@ -472,9 +394,7 @@ func (ctl *Control) controler() {
return
}
time.Sleep(time.Second)
// loop util reconnect to server success
// loop util reconnecting to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
@@ -487,27 +407,27 @@ func (ctl *Control) controler() {
}
continue
}
// reconnect success, init the delayTime
// reconnect success, init delayTime
delayTime = time.Second
break
}
// init related channels and variables
ctl.init()
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
ctl.readerShutdown = shutdown.New()
ctl.writerShutdown = shutdown.New()
ctl.msgHandlerShutdown = shutdown.New()
ctl.pm.Reset(ctl.sendCh, ctl.runId)
// previous work goroutines should be closed and start them here
go ctl.manager()
go ctl.msgHandler()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
ctl.mu.RUnlock()
// start all configured proxies
ctl.pm.CheckAndStartProxy()
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
@@ -516,106 +436,7 @@ func (ctl *Control) controler() {
}
}
func (ctl *Control) setRunId(runId string) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.runId = runId
}
func (ctl *Control) getRunId() string {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
return ctl.runId
}
func (ctl *Control) getProxy(name string) (pxy Proxy, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
pxy, ok = ctl.proxies[name]
return
}
func (ctl *Control) addProxy(name string, pxy Proxy) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.proxies[name] = pxy
}
func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
conf, ok = ctl.pxyCfgs[name]
return
}
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
removedPxyNames := make([]string, 0)
for name, oldCfg := range ctl.pxyCfgs {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}
if del {
removedPxyNames = append(removedPxyNames, name)
delete(ctl.pxyCfgs, name)
if pxy, ok := ctl.proxies[name]; ok {
pxy.Close()
}
delete(ctl.proxies, name)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
}
ctl.Info("proxy removed: %v", removedPxyNames)
addedPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := ctl.pxyCfgs[name]; !ok {
ctl.pxyCfgs[name] = cfg
addedPxyNames = append(addedPxyNames, name)
}
}
ctl.Info("proxy added: %v", addedPxyNames)
removedVistorName := make([]string, 0)
for name, oldVistorCfg := range ctl.vistorCfgs {
del := false
cfg, ok := vistorCfgs[name]
if !ok {
del = true
} else {
if !oldVistorCfg.Compare(cfg) {
del = true
}
}
if del {
removedVistorName = append(removedVistorName, name)
delete(ctl.vistorCfgs, name)
if vistor, ok := ctl.vistors[name]; ok {
vistor.Close()
}
delete(ctl.vistors, name)
}
}
ctl.Info("vistor removed: %v", removedVistorName)
addedVistorName := make([]string, 0)
for name, vistorCfg := range vistorCfgs {
if _, ok := ctl.vistorCfgs[name]; !ok {
ctl.vistorCfgs[name] = vistorCfg
addedVistorName = append(addedVistorName, name)
}
}
ctl.Info("vistor added: %v", addedVistorName)
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
err := ctl.pm.Reload(pxyCfgs, visitorCfgs)
return err
}

View File

@@ -15,6 +15,7 @@
package client
import (
"bytes"
"fmt"
"io"
"net"
@@ -29,6 +30,7 @@ import (
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
)
// Proxy defines how to deal with work connections for different proxy type.
@@ -37,13 +39,13 @@ type Proxy interface {
// InWorkConn accept work connections registered to server.
InWorkConn(conn frpNet.Conn)
Close()
log.Logger
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
baseProxy := BaseProxy{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
@@ -72,12 +74,16 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.XtcpProxyConf:
pxy = &XtcpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
}
return
}
type BaseProxy struct {
ctl *Control
closed bool
mu sync.RWMutex
log.Logger
@@ -108,7 +114,8 @@ func (pxy *TcpProxy) Close() {
}
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
}
// HTTP
@@ -136,7 +143,8 @@ func (pxy *HttpProxy) Close() {
}
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
}
// HTTPS
@@ -164,7 +172,8 @@ func (pxy *HttpsProxy) Close() {
}
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
}
// STCP
@@ -192,7 +201,101 @@ func (pxy *StcpProxy) Close() {
}
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
[]byte(config.ClientCommonCfg.PrivilegeToken))
}
// XTCP
type XtcpProxy struct {
BaseProxy
cfg *config.XtcpProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *XtcpProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
return
}
}
return
}
func (pxy *XtcpProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
defer conn.Close()
var natHoleSidMsg msg.NatHoleSid
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
if err != nil {
pxy.Error("xtcp read from workConn error: %v", err)
return
}
natHoleClientMsg := &msg.NatHoleClient{
ProxyName: pxy.cfg.ProxyName,
Sid: natHoleSidMsg.Sid,
}
raddr, _ := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
clientConn, err := net.DialUDP("udp", nil, raddr)
defer clientConn.Close()
err = msg.WriteMsg(clientConn, natHoleClientMsg)
if err != nil {
pxy.Error("send natHoleClientMsg to server error: %v", err)
return
}
// Wait for client address at most 5 seconds.
var natHoleRespMsg msg.NatHoleResp
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
buf := pool.GetBuf(1024)
n, err := clientConn.Read(buf)
if err != nil {
pxy.Error("get natHoleRespMsg error: %v", err)
return
}
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
if err != nil {
pxy.Error("get natHoleRespMsg error: %v", err)
return
}
clientConn.SetReadDeadline(time.Time{})
clientConn.Close()
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Send sid to visitor udp address.
time.Sleep(time.Second)
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
if err != nil {
pxy.Error("resolve visitor udp address error: %v", err)
return
}
lConn, err := net.DialUDP("udp", laddr, daddr)
if err != nil {
pxy.Error("dial visitor udp address error: %v", err)
return
}
lConn.Write([]byte(natHoleRespMsg.Sid))
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
if err != nil {
pxy.Error("create kcp connection from udp connection error: %v", err)
return
}
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
}
// UDP
@@ -302,15 +405,16 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
// Common handler for tcp work connections.
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
var (
remote io.ReadWriteCloser
err error
)
remote = workConn
defer remote.Close()
if baseInfo.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
remote, err = frpIo.WithEncryption(remote, encKey)
if err != nil {
workConn.Error("create encryption stream error: %v", err)
return
@@ -323,7 +427,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
if proxyPlugin != nil {
// if plugin is set, let plugin handle connections first
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
proxyPlugin.Handle(remote)
proxyPlugin.Handle(remote, workConn)
workConn.Debug("handle by plugin finished")
return
} else {

349
client/proxy_manager.go Normal file
View File

@@ -0,0 +1,349 @@
package client
import (
"fmt"
"sync"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
)
const (
ProxyStatusNew = "new"
ProxyStatusStartErr = "start error"
ProxyStatusRunning = "running"
ProxyStatusClosed = "closed"
)
type ProxyManager struct {
ctl *Control
proxies map[string]*ProxyWrapper
visitorCfgs map[string]config.ProxyConf
visitors map[string]Visitor
sendCh chan (msg.Message)
closed bool
mu sync.RWMutex
log.Logger
}
type ProxyWrapper struct {
Name string
Type string
Status string
Err string
Cfg config.ProxyConf
RemoteAddr string
pxy Proxy
mu sync.RWMutex
}
type ProxyStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Err string `json:"err"`
Cfg config.ProxyConf `json:"cfg"`
// Got from server.
RemoteAddr string `json:"remote_addr"`
}
func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper {
return &ProxyWrapper{
Name: cfg.GetName(),
Type: cfg.GetType(),
Status: ProxyStatusNew,
Cfg: cfg,
pxy: nil,
}
}
func (pw *ProxyWrapper) IsRunning() bool {
pw.mu.RLock()
defer pw.mu.RUnlock()
if pw.Status == ProxyStatusRunning {
return true
} else {
return false
}
}
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
pw.mu.RLock()
defer pw.mu.RUnlock()
ps := &ProxyStatus{
Name: pw.Name,
Type: pw.Type,
Status: pw.Status,
Err: pw.Err,
Cfg: pw.Cfg,
RemoteAddr: pw.RemoteAddr,
}
return ps
}
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
if pw.pxy != nil {
pw.pxy.Close()
pw.pxy = nil
}
if serverRespErr != "" {
pw.mu.Lock()
pw.Status = ProxyStatusStartErr
pw.RemoteAddr = remoteAddr
pw.Err = serverRespErr
pw.mu.Unlock()
return fmt.Errorf(serverRespErr)
}
pxy := NewProxy(pw.Cfg)
pw.mu.Lock()
defer pw.mu.Unlock()
pw.RemoteAddr = remoteAddr
if err := pxy.Run(); err != nil {
pw.Status = ProxyStatusStartErr
pw.Err = err.Error()
return err
}
pw.Status = ProxyStatusRunning
pw.Err = ""
pw.pxy = pxy
return nil
}
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
pw.mu.RLock()
pxy := pw.pxy
pw.mu.RUnlock()
if pxy != nil {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
} else {
workConn.Close()
}
}
func (pw *ProxyWrapper) Close() {
pw.mu.Lock()
defer pw.mu.Unlock()
if pw.pxy != nil {
pw.pxy.Close()
pw.pxy = nil
}
pw.Status = ProxyStatusClosed
}
func NewProxyManager(ctl *Control, msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
return &ProxyManager{
ctl: ctl,
proxies: make(map[string]*ProxyWrapper),
visitorCfgs: make(map[string]config.ProxyConf),
visitors: make(map[string]Visitor),
sendCh: msgSendCh,
closed: false,
Logger: log.NewPrefixLogger(logPrefix),
}
}
func (pm *ProxyManager) Reset(msgSendCh chan (msg.Message), logPrefix string) {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.closed = false
pm.sendCh = msgSendCh
pm.ClearLogPrefix()
pm.AddLogPrefix(logPrefix)
}
// Must hold the lock before calling this function.
func (pm *ProxyManager) sendMsg(m msg.Message) error {
err := errors.PanicToError(func() {
pm.sendCh <- m
})
if err != nil {
pm.closed = true
}
return err
}
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if pm.closed {
return fmt.Errorf("ProxyManager is closed now")
}
pxy, ok := pm.proxies[name]
if !ok {
return fmt.Errorf("no proxy found")
}
if err := pxy.Start(remoteAddr, serverRespErr); err != nil {
errRet := err
err = pm.sendMsg(&msg.CloseProxy{
ProxyName: name,
})
if err != nil {
errRet = fmt.Errorf("send CloseProxy message error")
}
return errRet
}
return nil
}
func (pm *ProxyManager) CloseProxies() {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
pxy.Close()
}
}
func (pm *ProxyManager) CheckAndStartProxy() {
pm.mu.RLock()
defer pm.mu.RUnlock()
if pm.closed {
pm.Warn("CheckAndStartProxy error: ProxyManager is closed now")
return
}
for _, pxy := range pm.proxies {
if !pxy.IsRunning() {
var newProxyMsg msg.NewProxy
pxy.Cfg.UnMarshalToMsg(&newProxyMsg)
err := pm.sendMsg(&newProxyMsg)
if err != nil {
pm.Warn("[%s] proxy send NewProxy message error")
return
}
}
}
for _, cfg := range pm.visitorCfgs {
if _, exist := pm.visitors[cfg.GetName()]; !exist {
pm.Info("try to start visitor [%s]", cfg.GetName())
visitor := NewVisitor(pm.ctl, cfg)
err := visitor.Run()
if err != nil {
visitor.Warn("start error: %v", err)
continue
}
pm.visitors[cfg.GetName()] = visitor
visitor.Info("start visitor success")
}
}
}
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if pm.closed {
err := fmt.Errorf("Reload error: ProxyManager is closed now")
pm.Warn(err.Error())
return err
}
delPxyNames := make([]string, 0)
for name, pxy := range pm.proxies {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !pxy.Cfg.Compare(cfg) {
del = true
}
}
if del {
delPxyNames = append(delPxyNames, name)
delete(pm.proxies, name)
pxy.Close()
err := pm.sendMsg(&msg.CloseProxy{
ProxyName: name,
})
if err != nil {
err = fmt.Errorf("Reload error: ProxyManager is closed now")
pm.Warn(err.Error())
return err
}
}
}
pm.Info("proxy removed: %v", delPxyNames)
addPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := pm.proxies[name]; !ok {
pxy := NewProxyWrapper(cfg)
pm.proxies[name] = pxy
addPxyNames = append(addPxyNames, name)
}
}
pm.Info("proxy added: %v", addPxyNames)
delVisitorName := make([]string, 0)
for name, oldVisitorCfg := range pm.visitorCfgs {
del := false
cfg, ok := visitorCfgs[name]
if !ok {
del = true
} else {
if !oldVisitorCfg.Compare(cfg) {
del = true
}
}
if del {
delVisitorName = append(delVisitorName, name)
delete(pm.visitorCfgs, name)
if visitor, ok := pm.visitors[name]; ok {
visitor.Close()
}
delete(pm.visitors, name)
}
}
pm.Info("visitor removed: %v", delVisitorName)
addVisitorName := make([]string, 0)
for name, visitorCfg := range visitorCfgs {
if _, ok := pm.visitorCfgs[name]; !ok {
pm.visitorCfgs[name] = visitorCfg
addVisitorName = append(addVisitorName, name)
}
}
pm.Info("visitor added: %v", addVisitorName)
return nil
}
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
pm.mu.RLock()
pw, ok := pm.proxies[name]
pm.mu.RUnlock()
if ok {
pw.InWorkConn(workConn)
} else {
workConn.Close()
}
}
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
ps := make([]*ProxyStatus, 0)
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
ps = append(ps, pxy.GetStatus())
}
return ps
}

View File

@@ -26,11 +26,11 @@ type Service struct {
closedCh chan int
}
func NewService(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) (svr *Service) {
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) {
svr = &Service{
closedCh: make(chan int),
}
ctl := NewControl(svr, pxyCfgs, vistorCfgs)
ctl := NewControl(svr, pxyCfgs, visitorCfgs)
svr.ctl = ctl
return
}
@@ -53,6 +53,6 @@ func (svr *Service) Run() error {
return nil
}
func (svr *Service) Close() error {
return svr.ctl.Close()
func (svr *Service) Close() {
svr.ctl.Close()
}

322
client/visitor.go Normal file
View File

@@ -0,0 +1,322 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"bytes"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/net/ipv4"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/frp/utils/util"
)
// Visitor is used for forward traffics from local port tot remote service.
type Visitor interface {
Run() error
Close()
log.Logger
}
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
baseVisitor := BaseVisitor{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
case *config.StcpProxyConf:
visitor = &StcpVisitor{
BaseVisitor: baseVisitor,
cfg: cfg,
}
case *config.XtcpProxyConf:
visitor = &XtcpVisitor{
BaseVisitor: baseVisitor,
cfg: cfg,
}
}
return
}
type BaseVisitor struct {
ctl *Control
l frpNet.Listener
closed bool
mu sync.RWMutex
log.Logger
}
type StcpVisitor struct {
BaseVisitor
cfg *config.StcpProxyConf
}
func (sv *StcpVisitor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
if err != nil {
return
}
go sv.worker()
return
}
func (sv *StcpVisitor) Close() {
sv.l.Close()
}
func (sv *StcpVisitor) worker() {
for {
conn, err := sv.l.Accept()
if err != nil {
sv.Warn("stcp local listener closed")
return
}
go sv.handleConn(conn)
}
}
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
defer userConn.Close()
sv.Debug("get a new stcp user connection")
visitorConn, err := sv.ctl.connectServer()
if err != nil {
return
}
defer visitorConn.Close()
now := time.Now().Unix()
newVisitorConnMsg := &msg.NewVisitorConn{
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
if err != nil {
sv.Warn("send newVisitorConnMsg to server error: %v", err)
return
}
var newVisitorConnRespMsg msg.NewVisitorConnResp
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
if err != nil {
sv.Warn("get newVisitorConnRespMsg error: %v", err)
return
}
visitorConn.SetReadDeadline(time.Time{})
if newVisitorConnRespMsg.Error != "" {
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
return
}
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil {
sv.Error("create encryption stream error: %v", err)
return
}
}
if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote)
}
frpIo.Join(userConn, remote)
}
type XtcpVisitor struct {
BaseVisitor
cfg *config.XtcpProxyConf
}
func (sv *XtcpVisitor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
if err != nil {
return
}
go sv.worker()
return
}
func (sv *XtcpVisitor) Close() {
sv.l.Close()
}
func (sv *XtcpVisitor) worker() {
for {
conn, err := sv.l.Accept()
if err != nil {
sv.Warn("stcp local listener closed")
return
}
go sv.handleConn(conn)
}
}
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
defer userConn.Close()
sv.Debug("get a new xtcp user connection")
if config.ClientCommonCfg.ServerUdpPort == 0 {
sv.Error("xtcp is not supported by server")
return
}
raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
visitorConn, err := net.DialUDP("udp", nil, raddr)
defer visitorConn.Close()
now := time.Now().Unix()
natHoleVisitorMsg := &msg.NatHoleVisitor{
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now,
}
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
if err != nil {
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
return
}
// Wait for client address at most 10 seconds.
var natHoleRespMsg msg.NatHoleResp
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
buf := pool.GetBuf(1024)
n, err := visitorConn.Read(buf)
if err != nil {
sv.Warn("get natHoleRespMsg error: %v", err)
return
}
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
if err != nil {
sv.Warn("get natHoleRespMsg error: %v", err)
return
}
visitorConn.SetReadDeadline(time.Time{})
pool.PutBuf(buf)
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
// Close visitorConn, so we can use it's local address.
visitorConn.Close()
// Send detect message.
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
if len(array) <= 1 {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
return
}
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
/*
for i := 1000; i < 65000; i++ {
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
}
*/
port, err := strconv.ParseInt(array[1], 10, 64)
if err != nil {
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
return
}
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
sv.Trace("send all detect msg done")
// Listen for visitorConn's address and wait for client connection.
lConn, err := net.ListenUDP("udp", laddr)
if err != nil {
sv.Error("listen on visitorConn's local adress error: %v", err)
return
}
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
sidBuf := pool.GetBuf(1024)
n, _, err = lConn.ReadFromUDP(sidBuf)
if err != nil {
sv.Warn("get sid from client error: %v", err)
return
}
lConn.SetReadDeadline(time.Time{})
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
sv.Warn("incorrect sid from client")
return
}
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
pool.PutBuf(sidBuf)
var remote io.ReadWriteCloser
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
if err != nil {
sv.Error("create kcp connection from udp connection error: %v", err)
return
}
if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil {
sv.Error("create encryption stream error: %v", err)
return
}
}
if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote)
}
frpIo.Join(userConn, remote)
sv.Debug("join connections closed")
}
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
return err
}
tConn, err := net.DialUDP("udp", laddr, daddr)
if err != nil {
return err
}
uConn := ipv4.NewConn(tConn)
uConn.SetTTL(3)
tConn.Write(content)
tConn.Close()
return nil
}

View File

@@ -1,145 +0,0 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"io"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
)
// Vistor is used for forward traffics from local port tot remote service.
type Vistor interface {
Run() error
Close()
log.Logger
}
func NewVistor(ctl *Control, pxyConf config.ProxyConf) (vistor Vistor) {
baseVistor := BaseVistor{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
case *config.StcpProxyConf:
vistor = &StcpVistor{
BaseVistor: baseVistor,
cfg: cfg,
}
}
return
}
type BaseVistor struct {
ctl *Control
l frpNet.Listener
closed bool
mu sync.RWMutex
log.Logger
}
type StcpVistor struct {
BaseVistor
cfg *config.StcpProxyConf
}
func (sv *StcpVistor) Run() (err error) {
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
if err != nil {
return
}
go sv.worker()
return
}
func (sv *StcpVistor) Close() {
sv.l.Close()
}
func (sv *StcpVistor) worker() {
for {
conn, err := sv.l.Accept()
if err != nil {
sv.Warn("stcp local listener closed")
return
}
go sv.handleConn(conn)
}
}
func (sv *StcpVistor) handleConn(userConn frpNet.Conn) {
defer userConn.Close()
sv.Debug("get a new stcp user connection")
vistorConn, err := sv.ctl.connectServer()
if err != nil {
return
}
defer vistorConn.Close()
now := time.Now().Unix()
newVistorConnMsg := &msg.NewVistorConn{
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
}
err = msg.WriteMsg(vistorConn, newVistorConnMsg)
if err != nil {
sv.Warn("send newVistorConnMsg to server error: %v", err)
return
}
var newVistorConnRespMsg msg.NewVistorConnResp
vistorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(vistorConn, &newVistorConnRespMsg)
if err != nil {
sv.Warn("get newVistorConnRespMsg error: %v", err)
return
}
vistorConn.SetReadDeadline(time.Time{})
if newVistorConnRespMsg.Error != "" {
sv.Warn("start new vistor connection error: %s", newVistorConnRespMsg.Error)
return
}
var remote io.ReadWriteCloser
remote = vistorConn
if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil {
sv.Error("create encryption stream error: %v", err)
return
}
}
if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote)
}
frpIo.Join(userConn, remote)
}

View File

@@ -28,6 +28,7 @@ import (
"time"
docopt "github.com/docopt/docopt-go"
"github.com/rodaine/table"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client"
@@ -44,7 +45,8 @@ var usage string = `frpc is the client of frp
Usage:
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
frpc [-c config_file] --reload
frpc reload [-c config_file]
frpc status [-c config_file]
frpc -h | --help
frpc -v | --version
@@ -53,14 +55,13 @@ Options:
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
--reload reload configure file without program exit
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frps.ini"
confFile := "./frpc.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
@@ -82,40 +83,25 @@ func main() {
config.ClientCommonCfg.ConfigFile = confFile
// check if reload command
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
if err != nil {
if args["reload"] != nil {
if args["reload"].(bool) {
if err = CmdReload(); err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
} else {
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
// check if status command
if args["status"] != nil {
if args["status"].(bool) {
if err = CmdStatus(); err != nil {
fmt.Printf("frps get status error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
@@ -146,7 +132,7 @@ func main() {
os.Exit(1)
}
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = serverPort
config.ClientCommonCfg.ServerPort = int(serverPort)
}
if args["-v"] != nil {
@@ -156,7 +142,7 @@ func main() {
}
}
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
if err != nil {
fmt.Println(err)
os.Exit(1)
@@ -165,7 +151,7 @@ func main() {
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
svr := client.NewService(pxyCfgs, vistorCfgs)
svr := client.NewService(pxyCfgs, visitorCfgs)
// Capture the exit signal if we use kcp.
if config.ClientCommonCfg.Protocol == "kcp" {
@@ -187,3 +173,133 @@ func HandleSignal(svr *client.Service) {
time.Sleep(250 * time.Millisecond)
os.Exit(0)
}
func CmdReload() error {
if config.ClientCommonCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
}
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} else if res.Code != 0 {
return fmt.Errorf(res.Msg)
}
}
return nil
}
func CmdStatus() error {
if config.ClientCommonCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
}
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/status", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
} else {
if resp.StatusCode != 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
res := &client.StatusResp{}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
}
fmt.Println("Proxy Status...")
if len(res.Tcp) > 0 {
fmt.Printf("TCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Tcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Udp) > 0 {
fmt.Printf("UDP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Udp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Http) > 0 {
fmt.Printf("HTTP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Http {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Https) > 0 {
fmt.Printf("HTTPS")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Https {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Stcp) > 0 {
fmt.Printf("STCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Stcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Xtcp) > 0 {
fmt.Printf("XTCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Xtcp {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
}
return nil
}

View File

@@ -91,7 +91,7 @@ func main() {
os.Exit(1)
}
config.ServerCommonCfg.BindAddr = addr[0]
config.ServerCommonCfg.BindPort = bindPort
config.ServerCommonCfg.BindPort = int(bindPort)
}
if args["-v"] != nil {

View File

@@ -66,6 +66,13 @@ use_compression = false
# remote port listen by frps
remote_port = 6001
[ssh_random]
type = tcp
local_ip = 127.0.0.1
local_port = 22
# if remote_port is 0, frps will assgin a random port for you
remote_port = 0
[dns]
type = udp
local_ip = 114.114.114.114
@@ -88,7 +95,7 @@ http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
subdomain = web01
custom_domains = web02.yourdomain.com
# locations is only useful for http type
# locations is only available for http type
locations = /,/pic
host_header_rewrite = example.com
@@ -119,25 +126,43 @@ plugin_http_passwd = abc
[secret_tcp]
# If the type is secret tcp, remote_port is useless
# Who want to connect local port should deploy another frpc with stcp proxy and role is vistor
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
type = stcp
# sk used for authentication for vistors
# sk used for authentication for visitors
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
# user of frpc should be same in both stcp server and stcp vistor
[secret_tcp_vistor]
# frpc role vistor -> frps -> frpc role server
role = vistor
# user of frpc should be same in both stcp server and stcp visitor
[secret_tcp_visitor]
# frpc role visitor -> frps -> frpc role server
role = visitor
type = stcp
# the server name you want to vistor
# the server name you want to visitor
server_name = secret_tcp
sk = abcdefg
# connect this address to vistor stcp server
# connect this address to visitor stcp server
bind_addr = 127.0.0.1
bind_port = 9000
use_encryption = false
use_compression = false
[p2p_tcp]
type = xtcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
[p2p_tcp_visitor]
role = visitor
type = xtcp
server_name = p2p_tcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 9001
use_encryption = false
use_compression = false

View File

@@ -5,6 +5,9 @@
bind_addr = 0.0.0.0
bind_port = 7000
# udp port to help make udp hole to penetrate nat
bind_udp_port = 7001
# udp port used for kcp protocol, it can be same with 'bind_port'
# if not set, kcp is disabled in frps
kcp_bind_port = 7000
@@ -16,7 +19,10 @@ kcp_bind_port = 7000
vhost_http_port = 80
vhost_https_port = 443
# set dashboard_port to view dashboard of frps
# set dashboard_addr and dashboard_port to view dashboard of frps
# dashboard_addr's default value is same with bind_addr
# dashboard is available only if dashboard_port is set
dashboard_addr = 0.0.0.0
dashboard_port = 7500
# dashboard user and pwd for basic auth protect, if not set, both default value is admin

77
glide.lock generated Normal file
View File

@@ -0,0 +1,77 @@
hash: 188e1149e415ff9cefab8db2cded030efae57558a0b9551795c5c7d0b0572a7b
updated: 2018-01-17T01:14:34.435613+08:00
imports:
- name: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/docopt/docopt-go
version: 784ddc588536785e7299f7272f39101f7faccc3f
- name: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages:
- logs
- name: github.com/fatedier/kcp-go
version: cd167d2f15f451b0f33780ce862fca97adc0331e
- name: github.com/golang/snappy
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
- name: github.com/julienschmidt/httprouter
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
- name: github.com/klauspost/cpuid
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
- name: github.com/klauspost/reedsolomon
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
- name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/rakyll/statik
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
subpackages:
- fs
- name: github.com/rodaine/table
version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd
- name: github.com/stretchr/testify
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
subpackages:
- assert
- name: github.com/templexxx/cpufeat
version: 3794dfbfb04749f896b521032f69383f24c3687e
- name: github.com/templexxx/reedsolomon
version: 7092926d7d05c415fabb892b1464a03f8228ab80
- name: github.com/templexxx/xor
version: 0af8e873c554da75f37f2049cdffda804533d44c
- name: github.com/tjfoc/gmsm
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
subpackages:
- sm4
- name: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- name: github.com/xtaci/kcp-go
version: df437e2b8ec365a336200f9d9da53441cf72ed47
- name: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- blowfish
- cast5
- pbkdf2
- salsa20
- salsa20/salsa
- tea
- twofish
- xtea
- name: golang.org/x/net
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
subpackages:
- bpf
- context
- internal/iana
- internal/socket
- ipv4
testImports: []

75
glide.yaml Normal file
View File

@@ -0,0 +1,75 @@
package: github.com/fatedier/frp
import:
- package: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5
- package: github.com/davecgh/go-spew
version: v1.1.0
subpackages:
- spew
- package: github.com/docopt/docopt-go
version: 0.6.2
- package: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages:
- logs
- package: github.com/fatedier/kcp-go
version: cd167d2f15f451b0f33780ce862fca97adc0331e
- package: github.com/golang/snappy
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
- package: github.com/julienschmidt/httprouter
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
- package: github.com/klauspost/cpuid
version: v1.0
- package: github.com/klauspost/reedsolomon
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
- package: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- package: github.com/pmezard/go-difflib
version: v1.0.0
subpackages:
- difflib
- package: github.com/rakyll/statik
version: v0.1.0
subpackages:
- fs
- package: github.com/stretchr/testify
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
subpackages:
- assert
- package: github.com/templexxx/cpufeat
version: 3794dfbfb04749f896b521032f69383f24c3687e
- package: github.com/templexxx/reedsolomon
version: 7092926d7d05c415fabb892b1464a03f8228ab80
- package: github.com/templexxx/xor
version: 0.1.2
- package: github.com/tjfoc/gmsm
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
subpackages:
- sm4
- package: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- package: github.com/xtaci/kcp-go
version: v3.17
- package: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- blowfish
- cast5
- pbkdf2
- salsa20
- salsa20/salsa
- tea
- twofish
- xtea
- package: golang.org/x/net
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
subpackages:
- bpf
- context
- internal/iana
- internal/socket
- ipv4
- package: github.com/rodaine/table
version: v1.0.0

View File

@@ -29,7 +29,8 @@ var ClientCommonCfg *ClientCommonConf
type ClientCommonConf struct {
ConfigFile string
ServerAddr string
ServerPort int64
ServerPort int
ServerUdpPort int // this is specified by login response message from frps
HttpProxy string
LogFile string
LogWay string
@@ -37,7 +38,7 @@ type ClientCommonConf struct {
LogMaxDays int64
PrivilegeToken string
AdminAddr string
AdminPort int64
AdminPort int
AdminUser string
AdminPwd string
PoolCount int
@@ -55,6 +56,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
ConfigFile: "./frpc.ini",
ServerAddr: "0.0.0.0",
ServerPort: 7000,
ServerUdpPort: 0,
HttpProxy: "",
LogFile: "console",
LogWay: "console",
@@ -91,7 +93,12 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
tmpStr, ok = conf.Get("common", "server_port")
if ok {
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: invalid server_port")
return
}
cfg.ServerPort = int(v)
}
tmpStr, ok = conf.Get("common", "http_proxy")
@@ -137,7 +144,10 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
tmpStr, ok = conf.Get("common", "admin_port")
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.AdminPort = v
cfg.AdminPort = int(v)
} else {
err = fmt.Errorf("Parse conf error: invalid admin_port")
return
}
}
@@ -201,7 +211,7 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
return
} else {
cfg.HeartBeatTimeout = v
@@ -212,7 +222,7 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
} else {
cfg.HeartBeatInterval = v
@@ -220,12 +230,12 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
}
if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
return
}
return

View File

@@ -23,7 +23,6 @@ import (
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
)
@@ -36,6 +35,7 @@ func init() {
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
}
// NewConfByType creates a empty ProxyConf object by proxyType.
@@ -51,6 +51,7 @@ func NewConfByType(proxyType string) ProxyConf {
type ProxyConf interface {
GetName() string
GetType() string
GetBaseInfo() *BaseProxyConf
LoadFromMsg(pMsg *msg.NewProxy)
LoadFromFile(name string, conf ini.Section) error
@@ -102,6 +103,10 @@ func (cfg *BaseProxyConf) GetName() string {
return cfg.ProxyName
}
func (cfg *BaseProxyConf) GetType() string {
return cfg.ProxyType
}
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
return cfg
}
@@ -157,7 +162,7 @@ func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
// Bind info
type BindInfoConf struct {
BindAddr string `json:"bind_addr"`
RemotePort int64 `json:"remote_port"`
RemotePort int `json:"remote_port"`
}
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
@@ -177,10 +182,13 @@ func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err err
var (
tmpStr string
ok bool
v int64
)
if tmpStr, ok = section["remote_port"]; ok {
if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
} else {
cfg.RemotePort = int(v)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
@@ -193,11 +201,6 @@ func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
}
func (cfg *BindInfoConf) check() (err error) {
if len(ServerCommonCfg.PrivilegeAllowPorts) != 0 {
if ok := util.ContainsPort(ServerCommonCfg.PrivilegeAllowPorts, cfg.RemotePort); !ok {
return fmt.Errorf("remote port [%d] isn't allowed", cfg.RemotePort)
}
}
return nil
}
@@ -594,7 +597,7 @@ type StcpProxyConf struct {
LocalSvrConf
PluginConf
// used in role vistor
// used in role visitor
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
@@ -631,15 +634,18 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
}
tmpStr := section["role"]
if tmpStr == "server" || tmpStr == "vistor" {
if tmpStr == "" {
tmpStr = "server"
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
cfg.Role = "server"
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
}
cfg.Sk = section["sk"]
if tmpStr == "vistor" {
if tmpStr == "visitor" {
prefix := section["prefix"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
@@ -672,10 +678,102 @@ func (cfg *StcpProxyConf) Check() (err error) {
return
}
// XTCP
type XtcpProxyConf struct {
BaseProxyConf
Role string `json:"role"`
Sk string `json:"sk"`
// used in role server
LocalSvrConf
PluginConf
// used in role visitor
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
}
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*XtcpProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName ||
cfg.BindAddr != cmpConf.BindAddr ||
cfg.BindPort != cmpConf.BindPort {
return false
}
return true
}
// Only for role server.
func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
tmpStr := section["role"]
if tmpStr == "" {
tmpStr = "server"
}
if tmpStr == "server" || tmpStr == "visitor" {
cfg.Role = tmpStr
} else {
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr)
}
cfg.Sk = section["sk"]
if tmpStr == "visitor" {
prefix := section["prefix"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
}
if tmpStr, ok := section["bind_port"]; ok {
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
}
}
return
}
func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *XtcpProxyConf) Check() (err error) {
return
}
// if len(startProxy) is 0, start all
// otherwise just start proxies in startProxy map
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
proxyConfs map[string]ProxyConf, vistorConfs map[string]ProxyConf, err error) {
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
if prefix != "" {
prefix += "."
@@ -686,7 +784,7 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
startAll = false
}
proxyConfs = make(map[string]ProxyConf)
vistorConfs = make(map[string]ProxyConf)
visitorConfs = make(map[string]ProxyConf)
for name, section := range conf {
_, shouldStart := startProxy[name]
if name != "common" && (startAll || shouldStart) {
@@ -694,12 +792,12 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
section["prefix"] = prefix
cfg, err := NewProxyConfFromFile(name, section)
if err != nil {
return proxyConfs, vistorConfs, err
return proxyConfs, visitorConfs, err
}
role := section["role"]
if role == "vistor" {
vistorConfs[prefix+name] = cfg
if role == "visitor" {
visitorConfs[prefix+name] = cfg
} else {
proxyConfs[prefix+name] = cfg
}

View File

@@ -19,7 +19,6 @@ import (
"strconv"
"strings"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
)
@@ -29,18 +28,20 @@ var ServerCommonCfg *ServerCommonConf
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
KcpBindPort int64
BindPort int
BindUdpPort int
KcpBindPort int
ProxyBindAddr string
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
VhostHttpPort int
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int64
VhostHttpsPort int
DashboardAddr string
// if DashboardPort equals 0, dashboard is not available
DashboardPort int64
DashboardPort int
DashboardUser string
DashboardPwd string
AssetsDir string
@@ -54,8 +55,7 @@ type ServerCommonConf struct {
SubDomainHost string
TcpMux bool
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts [][2]int64
PrivilegeAllowPorts map[int]struct{}
MaxPoolCount int64
HeartBeatTimeout int64
UserConnTimeout int64
@@ -63,29 +63,32 @@ type ServerCommonConf struct {
func GetDefaultServerCommonConf() *ServerCommonConf {
return &ServerCommonConf{
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeMode: true,
PrivilegeToken: "",
AuthTimeout: 900,
SubDomainHost: "",
TcpMux: true,
MaxPoolCount: 5,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
BindUdpPort: 0,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardAddr: "0.0.0.0",
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeMode: true,
PrivilegeToken: "",
AuthTimeout: 900,
SubDomainHost: "",
TcpMux: true,
PrivilegeAllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
}
}
@@ -105,17 +108,31 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.BindPort = v
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_port")
return
} else {
cfg.BindPort = int(v)
}
}
tmpStr, ok = conf.Get("common", "bind_udp_port")
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
return
} else {
cfg.BindUdpPort = int(v)
}
}
tmpStr, ok = conf.Get("common", "kcp_bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v > 0 {
cfg.KcpBindPort = v
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
return
} else {
cfg.KcpBindPort = int(v)
}
}
@@ -128,10 +145,11 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
return
} else {
cfg.VhostHttpPort = int(v)
}
} else {
cfg.VhostHttpPort = 0
@@ -139,21 +157,30 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
return
} else {
cfg.VhostHttpsPort = int(v)
}
} else {
cfg.VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_addr")
if ok {
cfg.DashboardAddr = tmpStr
} else {
cfg.DashboardAddr = cfg.BindAddr
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
return
} else {
cfg.DashboardPort = int(v)
}
} else {
cfg.DashboardPort = 0
@@ -209,12 +236,45 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
// TODO: check if conflicts exist in port ranges
if ok {
cfg.PrivilegeAllowPorts, err = util.GetPortRanges(allowPortsStr)
if err != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
return
// e.g. 1000-2000,2001,2002,3000-4000
portRanges := strings.Split(allowPortsStr, ",")
for _, portRangeStr := range portRanges {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(portArray)
if rangeType == 1 {
// single port
singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
cfg.PrivilegeAllowPorts[int(singlePort)] = struct{}{}
} else if rangeType == 2 {
// range ports
min, errRet := strconv.ParseInt(portArray[0], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
max, errRet := strconv.ParseInt(portArray[1], 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
return
}
if max < min {
err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
return
}
for i := min; i <= max; i++ {
cfg.PrivilegeAllowPorts[int(i)] = struct{}{}
}
} else {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
return
}
}
}
}

View File

@@ -28,4 +28,5 @@ var (
HttpProxy string = "http"
HttpsProxy string = "https"
StcpProxy string = "stcp"
XtcpProxy string = "xtcp"
)

View File

@@ -20,19 +20,23 @@ import (
)
const (
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeCloseProxy = 'c'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypeNewVistorConn = 'v'
TypeNewVistorConnResp = '3'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeCloseProxy = 'c'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypeNewVisitorConn = 'v'
TypeNewVisitorConnResp = '3'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
TypeNatHoleVisitor = 'i'
TypeNatHoleClient = 'n'
TypeNatHoleResp = 'm'
TypeNatHoleSid = '5'
)
var (
@@ -52,11 +56,15 @@ func init() {
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
TypeMap[TypeNewVistorConn] = reflect.TypeOf(NewVistorConn{})
TypeMap[TypeNewVistorConnResp] = reflect.TypeOf(NewVistorConnResp{})
TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
TypeMap[TypePing] = reflect.TypeOf(Ping{})
TypeMap[TypePong] = reflect.TypeOf(Pong{})
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
for k, v := range TypeMap {
TypeStringMap[v] = k
@@ -82,9 +90,10 @@ type Login struct {
}
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
Error string `json:"error"`
Version string `json:"version"`
RunId string `json:"run_id"`
ServerUdpPort int `json:"server_udp_port"`
Error string `json:"error"`
}
// When frpc login success, send this message to frps for running a new proxy.
@@ -95,7 +104,7 @@ type NewProxy struct {
UseCompression bool `json:"use_compression"`
// tcp and udp only
RemotePort int64 `json:"remote_port"`
RemotePort int `json:"remote_port"`
// http and https only
CustomDomains []string `json:"custom_domains"`
@@ -110,8 +119,9 @@ type NewProxy struct {
}
type NewProxyResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
ProxyName string `json:"proxy_name"`
RemoteAddr string `json:"remote_addr"`
Error string `json:"error"`
}
type CloseProxy struct {
@@ -129,7 +139,7 @@ type StartWorkConn struct {
ProxyName string `json:"proxy_name"`
}
type NewVistorConn struct {
type NewVisitorConn struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
@@ -137,7 +147,7 @@ type NewVistorConn struct {
UseCompression bool `json:"use_compression"`
}
type NewVistorConnResp struct {
type NewVisitorConnResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
}
@@ -153,3 +163,24 @@ type UdpPacket struct {
LocalAddr *net.UDPAddr `json:"l"`
RemoteAddr *net.UDPAddr `json:"r"`
}
type NatHoleVisitor struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
}
type NatHoleClient struct {
ProxyName string `json:"proxy_name"`
Sid string `json:"sid"`
}
type NatHoleResp struct {
Sid string `json:"sid"`
VisitorAddr string `json:"visitor_addr"`
ClientAddr string `json:"client_addr"`
}
type NatHoleSid struct {
Sid string `json:"sid"`
}

View File

@@ -106,13 +106,8 @@ func (hp *HttpProxy) Name() string {
return PluginHttpProxy
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) {
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := frpNet.NewShareConn(wrapConn)
request, err := http.ReadRequest(bufio.NewReader(rd))

View File

@@ -17,6 +17,8 @@ package plugin
import (
"fmt"
"io"
frpNet "github.com/fatedier/frp/utils/net"
)
// Creators is used for create plugins to handle connections.
@@ -40,6 +42,6 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
type Plugin interface {
Name() string
Handle(conn io.ReadWriteCloser)
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
Close() error
}

View File

@@ -43,16 +43,9 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
return
}
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser) {
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
defer conn.Close()
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
}
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.Server.ServeConn(wrapConn)
}

View File

@@ -20,6 +20,7 @@ import (
"net"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
)
const PluginUnixDomainSocket = "unix_domain_socket"
@@ -51,7 +52,7 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
return
}
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser) {
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
if err != nil {
return

View File

@@ -97,9 +97,10 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
// Start send a login success message to client and start working.
func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
Error: "",
Version: version.Full(),
RunId: ctl.runId,
ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
Error: "",
}
msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -252,13 +253,13 @@ func (ctl *Control) stoper() {
ctl.allShutdown.WaitStart()
close(ctl.readCh)
ctl.managerShutdown.WaitDown()
ctl.managerShutdown.WaitDone()
close(ctl.sendCh)
ctl.writerShutdown.WaitDown()
ctl.writerShutdown.WaitDone()
ctl.conn.Close()
ctl.readerShutdown.WaitDown()
ctl.readerShutdown.WaitDone()
close(ctl.workConnCh)
for workConn := range ctl.workConnCh {
@@ -307,7 +308,7 @@ func (ctl *Control) manager() {
switch m := rawMsg.(type) {
case *msg.NewProxy:
// register proxy in this control
err := ctl.RegisterProxy(m)
remoteAddr, err := ctl.RegisterProxy(m)
resp := &msg.NewProxyResp{
ProxyName: m.ProxyName,
}
@@ -315,6 +316,7 @@ func (ctl *Control) manager() {
resp.Error = err.Error()
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} else {
resp.RemoteAddr = remoteAddr
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
StatsNewProxy(m.ProxyName, m.ProxyType)
}
@@ -331,24 +333,24 @@ func (ctl *Control) manager() {
}
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf
// Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConf(pxyMsg)
if err != nil {
return err
return
}
// NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := NewProxy(ctl, pxyConf)
if err != nil {
return err
return remoteAddr, err
}
err = pxy.Run()
remoteAddr, err = pxy.Run()
if err != nil {
return err
return
}
defer func() {
if err != nil {
@@ -358,13 +360,13 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
if err != nil {
return err
return
}
ctl.mu.Lock()
ctl.proxies[pxy.GetName()] = pxy
ctl.mu.Unlock()
return nil
return
}
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {

View File

@@ -32,7 +32,7 @@ var (
httpServerWriteTimeout = 10 * time.Second
)
func RunDashboardServer(addr string, port int64) (err error) {
func RunDashboardServer(addr string, port int) (err error) {
// url router
router := httprouter.New()

View File

@@ -36,8 +36,8 @@ type ServerInfoResp struct {
GeneralResponse
Version string `json:"version"`
VhostHttpPort int64 `json:"vhost_http_port"`
VhostHttpsPort int64 `json:"vhost_https_port"`
VhostHttpPort int `json:"vhost_http_port"`
VhostHttpsPort int `json:"vhost_https_port"`
AuthTimeout int64 `json:"auth_timeout"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`

View File

@@ -93,46 +93,46 @@ func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
return
}
// Manager for vistor listeners.
type VistorManager struct {
vistorListeners map[string]*frpNet.CustomListener
skMap map[string]string
// Manager for visitor listeners.
type VisitorManager struct {
visitorListeners map[string]*frpNet.CustomListener
skMap map[string]string
mu sync.RWMutex
}
func NewVistorManager() *VistorManager {
return &VistorManager{
vistorListeners: make(map[string]*frpNet.CustomListener),
skMap: make(map[string]string),
func NewVisitorManager() *VisitorManager {
return &VisitorManager{
visitorListeners: make(map[string]*frpNet.CustomListener),
skMap: make(map[string]string),
}
}
func (vm *VistorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
vm.mu.Lock()
defer vm.mu.Unlock()
if _, ok := vm.vistorListeners[name]; ok {
if _, ok := vm.visitorListeners[name]; ok {
err = fmt.Errorf("custom listener for [%s] is repeated", name)
return
}
l = frpNet.NewCustomListener()
vm.vistorListeners[name] = l
vm.visitorListeners[name] = l
vm.skMap[name] = sk
return
}
func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
useEncryption bool, useCompression bool) (err error) {
vm.mu.RLock()
defer vm.mu.RUnlock()
if l, ok := vm.vistorListeners[name]; ok {
if l, ok := vm.visitorListeners[name]; ok {
var sk string
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
err = fmt.Errorf("vistor connection of [%s] auth failed", name)
err = fmt.Errorf("visitor connection of [%s] auth failed", name)
return
}
@@ -146,7 +146,7 @@ func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64,
if useCompression {
rwc = frpIo.WithCompression(rwc)
}
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc))
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn))
} else {
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
return
@@ -154,10 +154,10 @@ func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64,
return
}
func (vm *VistorManager) CloseListener(name string) {
func (vm *VisitorManager) CloseListener(name string) {
vm.mu.Lock()
defer vm.mu.Unlock()
delete(vm.vistorListeners, name)
delete(vm.visitorListeners, name)
delete(vm.skMap, name)
}

182
server/nathole.go Normal file
View File

@@ -0,0 +1,182 @@
package server
import (
"bytes"
"fmt"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
"github.com/fatedier/frp/utils/util"
)
// Timeout seconds.
var NatHoleTimeout int64 = 10
type NatHoleController struct {
listener *net.UDPConn
clientCfgs map[string]*NatHoleClientCfg
sessions map[string]*NatHoleSession
mu sync.RWMutex
}
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
if err != nil {
return nil, err
}
lconn, err := net.ListenUDP("udp", addr)
if err != nil {
return nil, err
}
nc = &NatHoleController{
listener: lconn,
clientCfgs: make(map[string]*NatHoleClientCfg),
sessions: make(map[string]*NatHoleSession),
}
return nc, nil
}
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
clientCfg := &NatHoleClientCfg{
Name: name,
Sk: sk,
SidCh: make(chan string),
}
nc.mu.Lock()
nc.clientCfgs[name] = clientCfg
nc.mu.Unlock()
return clientCfg.SidCh
}
func (nc *NatHoleController) CloseClient(name string) {
nc.mu.Lock()
defer nc.mu.Unlock()
delete(nc.clientCfgs, name)
}
func (nc *NatHoleController) Run() {
for {
buf := pool.GetBuf(1024)
n, raddr, err := nc.listener.ReadFromUDP(buf)
if err != nil {
log.Trace("nat hole listener read from udp error: %v", err)
return
}
rd := bytes.NewReader(buf[:n])
rawMsg, err := msg.ReadMsg(rd)
if err != nil {
log.Trace("read nat hole message error: %v", err)
continue
}
switch m := rawMsg.(type) {
case *msg.NatHoleVisitor:
go nc.HandleVisitor(m, raddr)
case *msg.NatHoleClient:
go nc.HandleClient(m, raddr)
default:
log.Trace("error nat hole message type")
continue
}
pool.PutBuf(buf)
}
}
func (nc *NatHoleController) GenSid() string {
t := time.Now().Unix()
id, _ := util.RandId()
return fmt.Sprintf("%d%s", t, id)
}
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
sid := nc.GenSid()
session := &NatHoleSession{
Sid: sid,
VisitorAddr: raddr,
NotifyCh: make(chan struct{}, 0),
}
nc.mu.Lock()
clientCfg, ok := nc.clientCfgs[m.ProxyName]
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
nc.mu.Unlock()
return
}
nc.sessions[sid] = session
nc.mu.Unlock()
log.Trace("handle visitor message, sid [%s]", sid)
defer func() {
nc.mu.Lock()
delete(nc.sessions, sid)
nc.mu.Unlock()
}()
err := errors.PanicToError(func() {
clientCfg.SidCh <- sid
})
if err != nil {
return
}
// Wait client connections.
select {
case <-session.NotifyCh:
resp := nc.GenNatHoleResponse(raddr, session)
log.Trace("send nat hole response to visitor")
nc.listener.WriteToUDP(resp, raddr)
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
return
}
}
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
nc.mu.RLock()
session, ok := nc.sessions[m.Sid]
nc.mu.RUnlock()
if !ok {
return
}
log.Trace("handle client message, sid [%s]", session.Sid)
session.ClientAddr = raddr
session.NotifyCh <- struct{}{}
resp := nc.GenNatHoleResponse(raddr, session)
log.Trace("send nat hole response to client")
nc.listener.WriteToUDP(resp, raddr)
}
func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
m := &msg.NatHoleResp{
Sid: session.Sid,
VisitorAddr: session.VisitorAddr.String(),
ClientAddr: session.ClientAddr.String(),
}
b := bytes.NewBuffer(nil)
err := msg.WriteMsg(b, m)
if err != nil {
return []byte("")
}
return b.Bytes()
}
type NatHoleSession struct {
Sid string
VisitorAddr *net.UDPAddr
ClientAddr *net.UDPAddr
NotifyCh chan struct{}
}
type NatHoleClientCfg struct {
Name string
Sk string
SidCh chan string
}

180
server/ports.go Normal file
View File

@@ -0,0 +1,180 @@
package server
import (
"errors"
"fmt"
"net"
"sync"
"time"
)
const (
MinPort = 1025
MaxPort = 65535
MaxPortReservedDuration = time.Duration(24) * time.Hour
CleanReservedPortsInterval = time.Hour
)
var (
ErrPortAlreadyUsed = errors.New("port already used")
ErrPortNotAllowed = errors.New("port not allowed")
ErrPortUnAvailable = errors.New("port unavailable")
ErrNoAvailablePort = errors.New("no available port")
)
type PortCtx struct {
ProxyName string
Port int
Closed bool
UpdateTime time.Time
}
type PortManager struct {
reservedPorts map[string]*PortCtx
usedPorts map[int]*PortCtx
freePorts map[int]struct{}
bindAddr string
netType string
mu sync.Mutex
}
func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager {
pm := &PortManager{
reservedPorts: make(map[string]*PortCtx),
usedPorts: make(map[int]*PortCtx),
freePorts: make(map[int]struct{}),
bindAddr: bindAddr,
netType: netType,
}
if len(allowPorts) > 0 {
for port, _ := range allowPorts {
pm.freePorts[port] = struct{}{}
}
} else {
for i := MinPort; i <= MaxPort; i++ {
pm.freePorts[i] = struct{}{}
}
}
go pm.cleanReservedPortsWorker()
return pm
}
func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) {
portCtx := &PortCtx{
ProxyName: name,
Closed: false,
UpdateTime: time.Now(),
}
var ok bool
pm.mu.Lock()
defer func() {
if err == nil {
portCtx.Port = realPort
}
pm.mu.Unlock()
}()
// check reserved ports first
if port == 0 {
if ctx, ok := pm.reservedPorts[name]; ok {
if pm.isPortAvailable(ctx.Port) {
realPort = ctx.Port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
return
}
}
}
if port == 0 {
// get random port
count := 0
maxTryTimes := 5
for k, _ := range pm.freePorts {
count++
if count > maxTryTimes {
break
}
if pm.isPortAvailable(k) {
realPort = k
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
break
}
}
if realPort == 0 {
err = ErrNoAvailablePort
}
} else {
// specified port
if _, ok = pm.freePorts[port]; ok {
if pm.isPortAvailable(port) {
realPort = port
pm.usedPorts[realPort] = portCtx
pm.reservedPorts[name] = portCtx
delete(pm.freePorts, realPort)
} else {
err = ErrPortUnAvailable
}
} else {
if _, ok = pm.usedPorts[port]; ok {
err = ErrPortAlreadyUsed
} else {
err = ErrPortNotAllowed
}
}
}
return
}
func (pm *PortManager) isPortAvailable(port int) bool {
if pm.netType == "udp" {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l, err := net.ListenUDP("udp", addr)
if err != nil {
return false
}
l.Close()
return true
} else {
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l.Close()
return true
}
}
func (pm *PortManager) Release(port int) {
pm.mu.Lock()
defer pm.mu.Unlock()
if ctx, ok := pm.usedPorts[port]; ok {
pm.freePorts[port] = struct{}{}
delete(pm.usedPorts, port)
ctx.Closed = true
ctx.UpdateTime = time.Now()
}
}
// Release reserved port if it isn't used in last 24 hours.
func (pm *PortManager) cleanReservedPortsWorker() {
for {
time.Sleep(CleanReservedPortsInterval)
pm.mu.Lock()
for name, ctx := range pm.reservedPorts {
if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration {
delete(pm.reservedPorts, name)
}
}
pm.mu.Unlock()
}
}

View File

@@ -19,6 +19,7 @@ import (
"fmt"
"io"
"net"
"strings"
"sync"
"time"
@@ -29,11 +30,12 @@ import (
frpIo "github.com/fatedier/frp/utils/io"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost"
)
type Proxy interface {
Run() error
Run() (remoteAddr string, err error)
GetControl() *Control
GetName() string
GetConf() config.ProxyConf
@@ -148,6 +150,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
BaseProxy: basePxy,
cfg: cfg,
}
case *config.XtcpProxyConf:
pxy = &XtcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
@@ -158,19 +165,34 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
realPort int
}
func (pxy *TcpProxy) Run() error {
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort)
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
pxy.realPort, err = pxy.ctl.svr.tcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return err
return
}
defer func() {
if err != nil {
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
}
}()
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort
listener, errRet := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.realPort)
if errRet != nil {
err = errRet
return
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
return
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
@@ -179,35 +201,45 @@ func (pxy *TcpProxy) GetConf() config.ProxyConf {
func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close()
pxy.ctl.svr.tcpPortManager.Release(pxy.realPort)
}
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
closeFuncs []func()
}
func (pxy *HttpProxy) Run() (err error) {
routeConfig := &vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
routeConfig := vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
CreateConnFn: pxy.GetRealConn,
}
locations := pxy.cfg.Locations
if len(locations) == 0 {
locations = []string{""}
}
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
if err != nil {
return err
return
}
l.AddLogPrefix(pxy.name)
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(config.ServerCommonCfg.VhostHttpPort)))
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
@@ -215,17 +247,20 @@ func (pxy *HttpProxy) Run() (err error) {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
if err != nil {
return err
return
}
l.AddLogPrefix(pxy.name)
tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(config.ServerCommonCfg.VhostHttpPort)))
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
remoteAddr = strings.Join(addrs, ",")
return
}
@@ -233,8 +268,42 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
tmpConn, errRet := pxy.GetWorkConnFromPool()
if errRet != nil {
err = errRet
return
}
var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(config.ServerCommonCfg.PrivilegeToken))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc)
}
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
StatsOpenConnection(pxy.GetName())
return
}
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
name := pxy.GetName()
StatsCloseConnection(name)
StatsAddTrafficIn(name, totalWrite)
StatsAddTrafficOut(name, totalRead)
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
for _, closeFn := range pxy.closeFuncs {
closeFn()
}
}
type HttpsProxy struct {
@@ -242,32 +311,38 @@ type HttpsProxy struct {
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
routeConfig := &vhost.VhostRouteConfig{}
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(config.ServerCommonCfg.VhostHttpsPort)))
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil {
err = errRet
return
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(config.ServerCommonCfg.VhostHttpsPort)))
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
remoteAddr = strings.Join(addrs, ",")
return
}
@@ -284,17 +359,18 @@ type StcpProxy struct {
cfg *config.StcpProxyConf
}
func (pxy *StcpProxy) Run() error {
listener, err := pxy.ctl.svr.vistorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if err != nil {
return err
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
listener, errRet := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if errRet != nil {
err = errRet
return
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
return
}
func (pxy *StcpProxy) GetConf() config.ProxyConf {
@@ -303,13 +379,64 @@ func (pxy *StcpProxy) GetConf() config.ProxyConf {
func (pxy *StcpProxy) Close() {
pxy.BaseProxy.Close()
pxy.ctl.svr.vistorManager.CloseListener(pxy.GetName())
pxy.ctl.svr.visitorManager.CloseListener(pxy.GetName())
}
type XtcpProxy struct {
BaseProxy
cfg *config.XtcpProxyConf
closeCh chan struct{}
}
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
if pxy.ctl.svr.natHoleController == nil {
pxy.Error("udp port for xtcp is not specified.")
err = fmt.Errorf("xtcp is not supported in frps")
return
}
sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
go func() {
for {
select {
case <-pxy.closeCh:
break
case sid := <-sidCh:
workConn, errRet := pxy.GetWorkConnFromPool()
if errRet != nil {
continue
}
m := &msg.NatHoleSid{
Sid: sid,
}
errRet = msg.WriteMsg(workConn, m)
if errRet != nil {
pxy.Warn("write nat hole sid package error, %v", errRet)
}
}
}
}()
return
}
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *XtcpProxy) Close() {
pxy.BaseProxy.Close()
pxy.ctl.svr.natHoleController.CloseClient(pxy.GetName())
errors.PanicToError(func() {
close(pxy.closeCh)
})
}
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
realPort int
// udpConn is the listener of udp packages
udpConn *net.UDPConn
@@ -329,15 +456,29 @@ type UdpProxy struct {
isClosed bool
}
func (pxy *UdpProxy) Run() (err error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort))
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
pxy.realPort, err = pxy.ctl.svr.udpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return err
return
}
udpConn, err := net.ListenUDP("udp", addr)
if err != nil {
defer func() {
if err != nil {
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
}
}()
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.realPort))
if errRet != nil {
err = errRet
return
}
udpConn, errRet := net.ListenUDP("udp", addr)
if errRet != nil {
err = errRet
pxy.Warn("listen udp port error: %v", err)
return err
return
}
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
@@ -452,7 +593,7 @@ func (pxy *UdpProxy) Run() (err error) {
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
pxy.Close()
}()
return nil
return remoteAddr, nil
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
@@ -476,6 +617,7 @@ func (pxy *UdpProxy) Close() {
close(pxy.readCh)
close(pxy.sendCh)
}
pxy.ctl.svr.udpPortManager.Release(pxy.realPort)
}
// HandleUserTcpConnection is used for incoming tcp user connections.

View File

@@ -16,6 +16,8 @@ package server
import (
"fmt"
"net"
"net/http"
"time"
"github.com/fatedier/frp/assets"
@@ -44,74 +46,89 @@ type Service struct {
// Accept connections using kcp.
kcpListener frpNet.Listener
// For http proxies, route requests to different clients by hostname and other infomation.
VhostHttpMuxer *vhost.HttpMuxer
// For https proxies, route requests to different clients by hostname and other infomation.
VhostHttpsMuxer *vhost.HttpsMuxer
httpReverseProxy *vhost.HttpReverseProxy
// Manage all controllers.
ctlManager *ControlManager
// Manage all proxies.
pxyManager *ProxyManager
// Manage all vistor listeners.
vistorManager *VistorManager
// Manage all visitor listeners.
visitorManager *VisitorManager
// Manage all tcp ports.
tcpPortManager *PortManager
// Manage all udp ports.
udpPortManager *PortManager
// Controller for nat hole connections.
natHoleController *NatHoleController
}
func NewService() (svr *Service, err error) {
cfg := config.ServerCommonCfg
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
vistorManager: NewVistorManager(),
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
visitorManager: NewVisitorManager(),
tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts),
udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts),
}
// Init assets.
err = assets.Load(config.ServerCommonCfg.AssetsDir)
err = assets.Load(cfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
// Listen for accepting connections from client.
svr.listener, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
log.Info("frps tcp listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
// Listen for accepting connections from client using kcp protocol.
if config.ServerCommonCfg.KcpBindPort > 0 {
svr.kcpListener, err = frpNet.ListenKcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort)
if cfg.KcpBindPort > 0 {
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KcpBindPort)
if err != nil {
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort, err)
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KcpBindPort, err)
return
}
log.Info("frps kcp listen on udp %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort)
}
// Create http vhost muxer.
if config.ServerCommonCfg.VhostHttpPort > 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
if cfg.VhostHttpPort > 0 {
rp := vhost.NewHttpReverseProxy()
svr.httpReverseProxy = rp
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
server := &http.Server{
Addr: address,
Handler: rp,
}
var l net.Listener
l, err = net.Listen("tcp", address)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
}
svr.VhostHttpMuxer, err = vhost.NewHttpMuxer(l, 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
return
}
log.Info("http service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
go server.Serve(l)
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
}
// Create https vhost muxer.
if config.ServerCommonCfg.VhostHttpsPort > 0 {
if cfg.VhostHttpsPort > 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
if err != nil {
err = fmt.Errorf("Create vhost https listener error, %v", err)
return
@@ -121,22 +138,38 @@ func NewService() (svr *Service, err error) {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
}
log.Info("https service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
}
// Create nat hole controller.
if cfg.BindUdpPort > 0 {
var nc *NatHoleController
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
nc, err = NewNatHoleController(addr)
if err != nil {
err = fmt.Errorf("Create nat hole controller error, %v", err)
return
}
svr.natHoleController = nc
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
}
// Create dashboard web server.
if config.ServerCommonCfg.DashboardPort > 0 {
err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
if cfg.DashboardPort > 0 {
err = RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
if err != nil {
err = fmt.Errorf("Create dashboard web server error, %v", err)
return
}
log.Info("Dashboard listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
}
return
}
func (svr *Service) Run() {
if svr.natHoleController != nil {
go svr.natHoleController.Run()
}
if config.ServerCommonCfg.KcpBindPort > 0 {
go svr.HandleListener(svr.kcpListener)
}
@@ -180,16 +213,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
}
case *msg.NewWorkConn:
svr.RegisterWorkConn(conn, m)
case *msg.NewVistorConn:
if err = svr.RegisterVistorConn(conn, m); err != nil {
case *msg.NewVisitorConn:
if err = svr.RegisterVisitorConn(conn, m); err != nil {
conn.Warn("%v", err)
msg.WriteMsg(conn, &msg.NewVistorConnResp{
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: err.Error(),
})
conn.Close()
} else {
msg.WriteMsg(conn, &msg.NewVistorConnResp{
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: "",
})
@@ -258,7 +291,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
ctl := NewControl(svr, ctlConn, loginMsg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDown()
oldCtl.allShutdown.WaitDone()
}
ctlConn.AddLogPrefix(loginMsg.RunId)
@@ -280,8 +313,8 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
return
}
func (svr *Service) RegisterVistorConn(vistorConn frpNet.Conn, newMsg *msg.NewVistorConn) error {
return svr.vistorManager.NewConn(newMsg.ProxyName, vistorConn, newMsg.Timestamp, newMsg.SignKey,
func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
return svr.visitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression)
}

View File

@@ -10,5 +10,11 @@ if [ -n "${pid}" ]; then
kill ${pid}
fi
pid=`ps aux|grep './../bin/frpc -c ./conf/auto_test_frpc_visitor.ini'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
rm -f ./frps.log
rm -f ./frpc.log
rm -f ./frpc_visitor.log

View File

@@ -5,31 +5,142 @@ log_file = ./frpc.log
# debug, info, warn, error
log_level = debug
privilege_token = 123456
admin_port = 10600
admin_user = abc
admin_pwd = abc
[echo]
[tcp_normal]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10711
use_encryption = true
use_compression = true
remote_port = 10801
[web]
type = http
[tcp_ec]
type = tcp
local_ip = 127.0.0.1
local_port = 10702
local_port = 10701
remote_port = 10901
use_encryption = true
use_compression = true
custom_domains = 127.0.0.1
[udp]
[udp_normal]
type = udp
local_ip = 127.0.0.1
local_port = 10703
remote_port = 10712
local_port = 10702
remote_port = 10802
[udp_ec]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 10902
use_encryption = true
use_compression = true
[unix_domain]
type = tcp
remote_port = 10704
remote_port = 10803
plugin = unix_domain_socket
plugin_unix_path = /tmp/frp_echo_server.sock
[stcp]
type = stcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 10701
[stcp_ec]
type = stcp
sk = abc
local_ip = 127.0.0.1
local_port = 10701
use_encryption = true
use_compression = true
[web01]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = 127.0.0.1
[web02]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test2.frp.com
host_header_rewrite = test2.frp.com
use_encryption = true
use_compression = true
[web03]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test3.frp.com
use_encryption = true
use_compression = true
host_header_rewrite = test3.frp.com
locations = /,/foo
[web04]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test3.frp.com
use_encryption = true
use_compression = true
host_header_rewrite = test3.frp.com
locations = /bar
[web05]
type = http
local_ip = 127.0.0.1
local_port = 10704
custom_domains = test5.frp.com
host_header_rewrite = test5.frp.com
use_encryption = true
use_compression = true
http_user = test
http_user = test
[tcp_port_not_allowed]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 20001
[tcp_port_unavailable]
type =tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10700
[tcp_port_normal]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 20002
[tcp_random_port]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 0
[udp_port_not_allowed]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 20001
[udp_port_normal]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 20002
[udp_random_port]
type = udp
local_ip = 127.0.0.1
local_port = 10702
remote_port = 0

View File

@@ -0,0 +1,25 @@
[common]
server_addr = 0.0.0.0
server_port = 10700
log_file = ./frpc_visitor.log
# debug, info, warn, error
log_level = debug
privilege_token = 123456
[stcp_visitor]
type = stcp
role = visitor
server_name = stcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 10805
[stcp_ec_visitor]
type = stcp
role = visitor
server_name = stcp_ec
sk = abc
bind_addr = 127.0.0.1
bind_port = 10905
use_encryption = true
use_compression = true

View File

@@ -1,7 +1,8 @@
[common]
bind_addr = 0.0.0.0
bind_port = 10700
vhost_http_port = 10710
vhost_http_port = 10804
log_file = ./frps.log
log_level = debug
privilege_token = 123456
privilege_allow_ports = 10000-20000,20002,30000-40000

View File

@@ -1,7 +1,6 @@
package tests
import (
"bufio"
"fmt"
"io"
"net"
@@ -11,8 +10,8 @@ import (
frpNet "github.com/fatedier/frp/utils/net"
)
func StartEchoServer() {
l, err := frpNet.ListenTcp("127.0.0.1", 10701)
func StartTcpEchoServer() {
l, err := frpNet.ListenTcp("127.0.0.1", TEST_TCP_PORT)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return
@@ -30,7 +29,7 @@ func StartEchoServer() {
}
func StartUdpEchoServer() {
l, err := frpNet.ListenUDP("127.0.0.1", 10703)
l, err := frpNet.ListenUDP("127.0.0.1", TEST_UDP_PORT)
if err != nil {
fmt.Printf("udp echo server listen error: %v\n", err)
return
@@ -48,7 +47,7 @@ func StartUdpEchoServer() {
}
func StartUnixDomainServer() {
unixPath := "/tmp/frp_echo_server.sock"
unixPath := TEST_UNIX_DOMAIN_ADDR
os.Remove(unixPath)
syscall.Umask(0)
l, err := net.Listen("unix", unixPath)
@@ -69,17 +68,20 @@ func StartUnixDomainServer() {
}
func echoWorker(c net.Conn) {
br := bufio.NewReader(c)
buf := make([]byte, 2048)
for {
buf, err := br.ReadString('\n')
if err == io.EOF {
break
}
n, err := c.Read(buf)
if err != nil {
fmt.Printf("echo server read error: %v\n", err)
return
if err == io.EOF {
c.Close()
break
} else {
fmt.Printf("echo server read error: %v\n", err)
return
}
}
c.Write([]byte(buf + "\n"))
c.Write(buf[:n])
}
}

View File

@@ -1,119 +1,228 @@
package tests
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"testing"
"time"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/server"
)
var (
ECHO_PORT int64 = 10711
UDP_ECHO_PORT int64 = 10712
HTTP_PORT int64 = 10710
ECHO_TEST_STR string = "Hello World\n"
HTTP_RES_STR string = "Hello World"
SERVER_ADDR = "127.0.0.1"
ADMIN_ADDR = "127.0.0.1:10600"
ADMIN_USER = "abc"
ADMIN_PWD = "abc"
TEST_STR = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
TEST_TCP_PORT int = 10701
TEST_TCP_FRP_PORT int = 10801
TEST_TCP_EC_FRP_PORT int = 10901
TEST_TCP_ECHO_STR string = "tcp type:" + TEST_STR
TEST_UDP_PORT int = 10702
TEST_UDP_FRP_PORT int = 10802
TEST_UDP_EC_FRP_PORT int = 10902
TEST_UDP_ECHO_STR string = "udp type:" + TEST_STR
TEST_UNIX_DOMAIN_ADDR string = "/tmp/frp_echo_server.sock"
TEST_UNIX_DOMAIN_FRP_PORT int = 10803
TEST_UNIX_DOMAIN_STR string = "unix domain type:" + TEST_STR
TEST_HTTP_PORT int = 10704
TEST_HTTP_FRP_PORT int = 10804
TEST_HTTP_NORMAL_STR string = "http normal string: " + TEST_STR
TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR
TEST_HTTP_BAR_STR string = "http bar string: " + TEST_STR
TEST_STCP_FRP_PORT int = 10805
TEST_STCP_EC_FRP_PORT int = 10905
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
ProxyTcpPortNotAllowed string = "tcp_port_not_allowed"
ProxyTcpPortUnavailable string = "tcp_port_unavailable"
ProxyTcpPortNormal string = "tcp_port_normal"
ProxyTcpRandomPort string = "tcp_random_port"
ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
ProxyUdpPortNormal string = "udp_port_normal"
ProxyUdpRandomPort string = "udp_random_port"
)
func init() {
go StartEchoServer()
go StartTcpEchoServer()
go StartUdpEchoServer()
go StartHttpServer()
go StartUnixDomainServer()
go StartHttpServer()
time.Sleep(500 * time.Millisecond)
}
func TestEchoServer(t *testing.T) {
c, err := frpNet.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", ECHO_PORT))
if err != nil {
t.Fatalf("connect to echo server error: %v", err)
}
timer := time.Now().Add(time.Duration(5) * time.Second)
c.SetDeadline(timer)
func TestTcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_TCP_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
c.Write([]byte(ECHO_TEST_STR + "\n"))
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_TCP_EC_FRP_PORT)
res, err = sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
}
br := bufio.NewReader(c)
buf, err := br.ReadString('\n')
if err != nil {
t.Fatalf("read from echo server error: %v", err)
}
func TestUdp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_UDP_FRP_PORT)
res, err := sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
if ECHO_TEST_STR != buf {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_UDP_EC_FRP_PORT)
res, err = sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
}
func TestUnixDomain(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_UNIX_DOMAIN_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_UNIX_DOMAIN_STR)
if assert.NoError(err) {
assert.Equal(TEST_UNIX_DOMAIN_STR, res)
}
}
func TestHttpServer(t *testing.T) {
client := &http.Client{}
req, _ := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d", HTTP_PORT), nil)
res, err := client.Do(req)
if err != nil {
t.Fatalf("do http request error: %v", err)
func TestStcp(t *testing.T) {
assert := assert.New(t)
// Normal
addr := fmt.Sprintf("127.0.0.1:%d", TEST_STCP_FRP_PORT)
res, err := sendTcpMsg(addr, TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(TEST_STCP_ECHO_STR, res)
}
if res.StatusCode == 200 {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("read from http server error: %v", err)
}
bodystr := string(body)
if bodystr != HTTP_RES_STR {
t.Fatalf("content from http server error [%s], correct string is [%s]", bodystr, HTTP_RES_STR)
}
} else {
t.Fatalf("http code from http server error [%d]", res.StatusCode)
// Encrytion and compression
addr = fmt.Sprintf("127.0.0.1:%d", TEST_STCP_EC_FRP_PORT)
res, err = sendTcpMsg(addr, TEST_STCP_ECHO_STR)
if assert.NoError(err) {
assert.Equal(TEST_STCP_ECHO_STR, res)
}
}
func TestUdpEchoServer(t *testing.T) {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:10712")
if err != nil {
t.Fatalf("do udp request error: %v", err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
t.Fatalf("dial udp server error: %v", err)
}
defer conn.Close()
_, err = conn.Write([]byte("hello frp\n"))
if err != nil {
t.Fatalf("write to udp server error: %v", err)
}
data := make([]byte, 20)
n, err := conn.Read(data)
if err != nil {
t.Fatalf("read from udp server error: %v", err)
func TestHttp(t *testing.T) {
assert := assert.New(t)
// web01
code, body, err := sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "", nil)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
if string(bytes.TrimSpace(data[:n])) != "hello frp" {
t.Fatalf("message got from udp server error, get %s", string(data[:n-1]))
// web02
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test2.frp.com", nil)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
// error host header
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil)
if assert.NoError(err) {
assert.Equal(404, code)
}
// web03
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_NORMAL_STR, body)
}
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_FOO_STR, body)
}
// web04
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", TEST_HTTP_FRP_PORT), "test3.frp.com", nil)
if assert.NoError(err) {
assert.Equal(200, code)
assert.Equal(TEST_HTTP_BAR_STR, body)
}
// web05
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", nil)
if assert.NoError(err) {
assert.Equal(401, code)
}
header := make(map[string]string)
header["Authorization"] = basicAuth("test", "test")
code, body, err = sendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", TEST_HTTP_FRP_PORT), "test5.frp.com", header)
if assert.NoError(err) {
assert.Equal(401, code)
}
}
func TestUnixDomainServer(t *testing.T) {
c, err := frpNet.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", 10704))
if err != nil {
t.Fatalf("connect to echo server error: %v", err)
}
timer := time.Now().Add(time.Duration(5) * time.Second)
c.SetDeadline(timer)
c.Write([]byte(ECHO_TEST_STR + "\n"))
br := bufio.NewReader(c)
buf, err := br.ReadString('\n')
if err != nil {
t.Fatalf("read from echo server error: %v", err)
func TestPrivilegeAllowPorts(t *testing.T) {
assert := assert.New(t)
// Port not allowed
status, err := getProxyStatus(ProxyTcpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, server.ErrPortNotAllowed.Error()))
}
if ECHO_TEST_STR != buf {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
status, err = getProxyStatus(ProxyUdpPortNotAllowed)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, server.ErrPortNotAllowed.Error()))
}
status, err = getProxyStatus(ProxyTcpPortUnavailable)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusStartErr, status.Status)
assert.True(strings.Contains(status.Err, server.ErrPortUnAvailable.Error()))
}
// Port normal
status, err = getProxyStatus(ProxyTcpPortNormal)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
status, err = getProxyStatus(ProxyUdpPortNormal)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
}
func TestRandomPort(t *testing.T) {
assert := assert.New(t)
// tcp
status, err := getProxyStatus(ProxyTcpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := sendTcpMsg(addr, TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_TCP_ECHO_STR, res)
}
// udp
status, err = getProxyStatus(ProxyUdpRandomPort)
if assert.NoError(err) {
addr := status.RemoteAddr
res, err := sendUdpMsg(addr, TEST_UDP_ECHO_STR)
assert.NoError(err)
assert.Equal(TEST_UDP_ECHO_STR, res)
}
}

View File

@@ -3,13 +3,30 @@ package tests
import (
"fmt"
"net/http"
"strings"
)
func StartHttpServer() {
http.HandleFunc("/", request)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", 10702), nil)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", TEST_HTTP_PORT), nil)
}
func request(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(HTTP_RES_STR))
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
strings.Contains(r.Host, "test5.frp.com") {
w.WriteHeader(200)
w.Write([]byte(TEST_HTTP_NORMAL_STR))
} else if strings.Contains(r.Host, "test3.frp.com") {
w.WriteHeader(200)
if strings.Contains(r.URL.Path, "foo") {
w.Write([]byte(TEST_HTTP_FOO_STR))
} else if strings.Contains(r.URL.Path, "bar") {
w.Write([]byte(TEST_HTTP_BAR_STR))
} else {
w.Write([]byte(TEST_HTTP_NORMAL_STR))
}
} else {
w.WriteHeader(404)
}
return
}

View File

@@ -3,6 +3,7 @@
./../bin/frps -c ./conf/auto_test_frps.ini &
sleep 1
./../bin/frpc -c ./conf/auto_test_frpc.ini &
./../bin/frpc -c ./conf/auto_test_frpc_visitor.ini &
# wait until proxies are connected
sleep 2

156
tests/util.go Normal file
View File

@@ -0,0 +1,156 @@
package tests
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"time"
"github.com/fatedier/frp/client"
frpNet "github.com/fatedier/frp/utils/net"
)
func getProxyStatus(name string) (status *client.ProxyStatusResp, err error) {
req, err := http.NewRequest("GET", "http://"+ADMIN_ADDR+"/api/status", nil)
if err != nil {
return status, err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(ADMIN_USER+":"+ADMIN_PWD))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return status, err
} else {
if resp.StatusCode != 200 {
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return status, err
}
allStatus := &client.StatusResp{}
err = json.Unmarshal(body, &allStatus)
if err != nil {
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
}
for _, s := range allStatus.Tcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Udp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Http {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Https {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Stcp {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.Xtcp {
if s.Name == name {
return &s, nil
}
}
}
return status, errors.New("no proxy status found")
}
func sendTcpMsg(addr string, msg string) (res string, err error) {
c, err := frpNet.ConnectTcpServer(addr)
if err != nil {
err = fmt.Errorf("connect to tcp server error: %v", err)
return
}
defer c.Close()
timer := time.Now().Add(5 * time.Second)
c.SetDeadline(timer)
c.Write([]byte(msg))
buf := make([]byte, 2048)
n, errRet := c.Read(buf)
if errRet != nil {
err = fmt.Errorf("read from tcp server error: %v", errRet)
return
}
return string(buf[:n]), nil
}
func sendUdpMsg(addr string, msg string) (res string, err error) {
udpAddr, errRet := net.ResolveUDPAddr("udp", addr)
if errRet != nil {
err = fmt.Errorf("resolve udp addr error: %v", err)
return
}
conn, errRet := net.DialUDP("udp", nil, udpAddr)
if errRet != nil {
err = fmt.Errorf("dial udp server error: %v", err)
return
}
defer conn.Close()
_, err = conn.Write([]byte(msg))
if err != nil {
err = fmt.Errorf("write to udp server error: %v", err)
return
}
buf := make([]byte, 2048)
n, errRet := conn.Read(buf)
if errRet != nil {
err = fmt.Errorf("read from udp server error: %v", err)
return
}
return string(buf[:n]), nil
}
func sendHttpMsg(method, url string, host string, header map[string]string) (code int, body string, err error) {
req, errRet := http.NewRequest(method, url, nil)
if errRet != nil {
err = errRet
return
}
if host != "" {
req.Host = host
}
for k, v := range header {
req.Header.Set(k, v)
}
resp, errRet := http.DefaultClient.Do(req)
if errRet != nil {
err = errRet
return
}
code = resp.StatusCode
buf, errRet := ioutil.ReadAll(resp.Body)
if errRet != nil {
err = errRet
return
}
body = string(buf)
return
}
func basicAuth(username, passwd string) string {
auth := username + ":" + passwd
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}

View File

@@ -49,32 +49,50 @@ func WrapConn(c net.Conn) Conn {
type WrapReadWriteCloserConn struct {
io.ReadWriteCloser
log.Logger
underConn net.Conn
}
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser) Conn {
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) Conn {
return &WrapReadWriteCloserConn{
ReadWriteCloser: rwc,
Logger: log.NewPrefixLogger(""),
underConn: underConn,
}
}
func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
if conn.underConn != nil {
return conn.underConn.LocalAddr()
}
return (*net.TCPAddr)(nil)
}
func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
if conn.underConn != nil {
return conn.underConn.RemoteAddr()
}
return (*net.TCPAddr)(nil)
}
func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetReadDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
if conn.underConn != nil {
return conn.underConn.SetWriteDeadline(t)
}
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
@@ -156,3 +174,38 @@ func (sc *SharedConn) WriteBuff(buffer []byte) (err error) {
_, err = sc.buf.Write(buffer)
return err
}
type StatsConn struct {
Conn
totalRead int64
totalWrite int64
statsFunc func(totalRead, totalWrite int64)
}
func WrapStatsConn(conn Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
return &StatsConn{
Conn: conn,
statsFunc: statsFunc,
}
}
func (statsConn *StatsConn) Read(p []byte) (n int, err error) {
n, err = statsConn.Conn.Read(p)
statsConn.totalRead += int64(n)
return
}
func (statsConn *StatsConn) Write(p []byte) (n int, err error) {
n, err = statsConn.Conn.Write(p)
statsConn.totalWrite += int64(n)
return
}
func (statsConn *StatsConn) Close() (err error) {
err = statsConn.Conn.Close()
if statsConn.statsFunc != nil {
statsConn.statsFunc(statsConn.totalRead, statsConn.totalWrite)
}
return
}

View File

@@ -20,7 +20,7 @@ import (
"github.com/fatedier/frp/utils/log"
kcp "github.com/xtaci/kcp-go"
kcp "github.com/fatedier/kcp-go"
)
type KcpListener struct {
@@ -31,7 +31,7 @@ type KcpListener struct {
log.Logger
}
func ListenKcp(bindAddr string, bindPort int64) (l *KcpListener, err error) {
func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
if err != nil {
return l, err
@@ -85,3 +85,17 @@ func (l *KcpListener) Close() error {
}
return nil
}
func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
if err != nil {
return nil, err
}
kcpConn.SetStreamMode(true)
kcpConn.SetWriteDelay(true)
kcpConn.SetNoDelay(1, 20, 2, 1)
kcpConn.SetMtu(1350)
kcpConn.SetWindowSize(1024, 1024)
kcpConn.SetACKNoDelay(false)
return kcpConn, nil
}

View File

@@ -33,7 +33,7 @@ type TcpListener struct {
log.Logger
}
func ListenTcp(bindAddr string, bindPort int64) (l *TcpListener, err error) {
func ListenTcp(bindAddr string, bindPort int) (l *TcpListener, err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err

View File

@@ -167,7 +167,7 @@ type UdpListener struct {
log.Logger
}
func ListenUDP(bindAddr string, bindPort int64) (l *UdpListener, err error) {
func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err

View File

@@ -19,19 +19,19 @@ import (
)
type Shutdown struct {
doing bool
ending bool
start chan struct{}
down chan struct{}
mu sync.Mutex
doing bool
ending bool
startCh chan struct{}
doneCh chan struct{}
mu sync.Mutex
}
func New() *Shutdown {
return &Shutdown{
doing: false,
ending: false,
start: make(chan struct{}),
down: make(chan struct{}),
doing: false,
ending: false,
startCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
}
@@ -40,12 +40,12 @@ func (s *Shutdown) Start() {
defer s.mu.Unlock()
if !s.doing {
s.doing = true
close(s.start)
close(s.startCh)
}
}
func (s *Shutdown) WaitStart() {
<-s.start
<-s.startCh
}
func (s *Shutdown) Done() {
@@ -53,10 +53,10 @@ func (s *Shutdown) Done() {
defer s.mu.Unlock()
if !s.ending {
s.ending = true
close(s.down)
close(s.doneCh)
}
}
func (s *Shutdown) WaitDown() {
<-s.down
func (s *Shutdown) WaitDone() {
<-s.doneCh
}

View File

@@ -17,5 +17,5 @@ func TestShutdown(t *testing.T) {
time.Sleep(time.Millisecond)
s.Done()
}()
s.WaitDown()
s.WaitDone()
}

View File

@@ -19,8 +19,6 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"strings"
)
// RandId return a rand string used in frp.
@@ -48,65 +46,11 @@ func GetAuthKey(token string, timestamp int64) (key string) {
return hex.EncodeToString(data)
}
// for example: rangeStr is "1000-2000,2001,2002,3000-4000", return an array as port ranges.
func GetPortRanges(rangeStr string) (portRanges [][2]int64, err error) {
// for example: 1000-2000,2001,2002,3000-4000
rangeArray := strings.Split(rangeStr, ",")
for _, portRangeStr := range rangeArray {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(portArray)
if rangeType == 1 {
singlePort, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return [][2]int64{}, err
}
portRanges = append(portRanges, [2]int64{singlePort, singlePort})
} else if rangeType == 2 {
min, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return [][2]int64{}, err
}
max, err := strconv.ParseInt(portArray[1], 10, 64)
if err != nil {
return [][2]int64{}, err
}
if max < min {
return [][2]int64{}, fmt.Errorf("range incorrect")
}
portRanges = append(portRanges, [2]int64{min, max})
} else {
return [][2]int64{}, fmt.Errorf("format error")
}
func CanonicalAddr(host string, port int) (addr string) {
if port == 80 || port == 443 {
addr = host
} else {
addr = fmt.Sprintf("%s:%d", host, port)
}
return portRanges, nil
}
func ContainsPort(portRanges [][2]int64, port int64) bool {
for _, pr := range portRanges {
if port >= pr[0] && port <= pr[1] {
return true
}
}
return false
}
func PortRangesCut(portRanges [][2]int64, port int64) [][2]int64 {
var tmpRanges [][2]int64
for _, pr := range portRanges {
if port >= pr[0] && port <= pr[1] {
leftRange := [2]int64{pr[0], port - 1}
rightRange := [2]int64{port + 1, pr[1]}
if leftRange[0] <= leftRange[1] {
tmpRanges = append(tmpRanges, leftRange)
}
if rightRange[0] <= rightRange[1] {
tmpRanges = append(tmpRanges, rightRange)
}
} else {
tmpRanges = append(tmpRanges, pr)
}
}
return tmpRanges
return
}

View File

@@ -20,67 +20,3 @@ func TestGetAuthKey(t *testing.T) {
t.Log(key)
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
}
func TestGetPortRanges(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
expect := [][2]int64{
[2]int64{2000, 3000},
[2]int64{3001, 3001},
[2]int64{4000, 50000},
}
actual, err := GetPortRanges(rangesStr)
assert.Nil(err)
t.Log(actual)
assert.Equal(expect, actual)
}
func TestContainsPort(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
portRanges, err := GetPortRanges(rangesStr)
assert.Nil(err)
type Case struct {
Port int64
Answer bool
}
cases := []Case{
Case{
Port: 3001,
Answer: true,
},
Case{
Port: 3002,
Answer: false,
},
Case{
Port: 44444,
Answer: true,
},
}
for _, elem := range cases {
ok := ContainsPort(portRanges, elem.Port)
assert.Equal(elem.Answer, ok)
}
}
func TestPortRangesCut(t *testing.T) {
assert := assert.New(t)
rangesStr := "2000-3000,3001,4000-50000"
portRanges, err := GetPortRanges(rangesStr)
assert.Nil(err)
expect := [][2]int64{
[2]int64{2000, 3000},
[2]int64{3001, 3001},
[2]int64{4000, 44443},
[2]int64{44445, 50000},
}
actual := PortRangesCut(portRanges, 44444)
t.Log(actual)
assert.Equal(expect, actual)
}

View File

@@ -19,37 +19,31 @@ import (
"strings"
)
var version string = "0.13.0"
var version string = "0.15.0"
func Full() string {
return version
}
func Proto(v string) int64 {
func getSubVersion(v string, position int) int64 {
arr := strings.Split(v, ".")
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[0], 10, 64)
res, _ := strconv.ParseInt(arr[position], 10, 64)
return res
}
func Proto(v string) int64 {
return getSubVersion(v, 0)
}
func Major(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[1], 10, 64)
return res
return getSubVersion(v, 1)
}
func Minor(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 3 {
return 0
}
res, _ := strconv.ParseInt(arr[2], 10, 64)
return res
return getSubVersion(v, 2)
}
// add every case there if server will not accept client's protocol and return false

186
utils/vhost/newhttp.go Normal file
View File

@@ -0,0 +1,186 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vhost
import (
"bytes"
"context"
"errors"
"log"
"net"
"net/http"
"strings"
"sync"
"time"
frpLog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/pool"
)
var (
responseHeaderTimeout = time.Duration(30) * time.Second
ErrRouterConfigConflict = errors.New("router config conflict")
ErrNoDomain = errors.New("no such domain")
)
func getHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
host = strs[0]
} else {
host = addr
}
return
}
type HttpReverseProxy struct {
proxy *ReverseProxy
tr *http.Transport
vhostRouter *VhostRouters
cfgMu sync.RWMutex
}
func NewHttpReverseProxy() *HttpReverseProxy {
rp := &HttpReverseProxy{
vhostRouter: NewVhostRouters(),
}
proxy := &ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
url := req.Context().Value("url").(string)
host := getHostFromAddr(req.Context().Value("host").(string))
host = rp.GetRealHost(host, url)
if host != "" {
req.Host = host
}
req.URL.Host = req.Host
},
Transport: &http.Transport{
ResponseHeaderTimeout: responseHeaderTimeout,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string))
return rp.CreateConnection(host, url)
},
},
BufferPool: newWrapPool(),
ErrorLog: log.New(newWrapLogger(), "", 0),
}
rp.proxy = proxy
return rp
}
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
rp.cfgMu.Lock()
defer rp.cfgMu.Unlock()
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
if ok {
return ErrRouterConfigConflict
} else {
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
}
return nil
}
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
rp.cfgMu.Lock()
defer rp.cfgMu.Unlock()
rp.vhostRouter.Del(domain, location)
}
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
vr, ok := rp.getVhost(domain, location)
if ok {
host = vr.payload.(*VhostRouteConfig).RewriteHost
}
return
}
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
if ok {
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
if fn != nil {
return fn()
}
}
return nil, ErrNoDomain
}
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location)
if ok {
checkUser := vr.payload.(*VhostRouteConfig).Username
checkPasswd := vr.payload.(*VhostRouteConfig).Password
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
return false
}
}
return true
}
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
rp.cfgMu.RLock()
defer rp.cfgMu.RUnlock()
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return
}
domainSplit := strings.Split(domain, ".")
if len(domainSplit) < 3 {
return vr, false
}
domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".")
vr, ok = rp.vhostRouter.Get(domain, location)
return
}
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain := getHostFromAddr(req.Host)
location := req.URL.Path
user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
rp.proxy.ServeHTTP(rw, req)
}
type wrapPool struct{}
func newWrapPool() *wrapPool { return &wrapPool{} }
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
type wrapLogger struct{}
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
func (l *wrapLogger) Write(p []byte) (n int, err error) {
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
return len(p), nil
}

370
utils/vhost/reverseproxy.go Normal file
View File

@@ -0,0 +1,370 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// HTTP reverse proxy handler
package vhost
import (
"context"
"io"
"log"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
// onExitFlushLoop is a callback set by tests to detect the state of the
// flushLoop() goroutine.
var onExitFlushLoop func()
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
type ReverseProxy struct {
// Director must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
Director func(*http.Request)
// The transport used to perform proxy requests.
// If nil, http.DefaultTransport is used.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body.
// If zero, no periodic flushing is done.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
// BufferPool optionally specifies a buffer pool to
// get byte slices for use by io.CopyBuffer when
// copying HTTP response bodies.
BufferPool BufferPool
// ModifyResponse is an optional function that
// modifies the Response from the backend.
// If it returns an error, the proxy returns a StatusBadGateway error.
ModifyResponse func(*http.Response) error
}
// A BufferPool is an interface for getting and returning temporary
// byte slices for use by io.CopyBuffer.
type BufferPool interface {
Get() []byte
Put([]byte)
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
// NewSingleHostReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
return &ReverseProxy{Director: director}
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func cloneHeader(h http.Header) http.Header {
h2 := make(http.Header, len(h))
for k, vv := range h {
vv2 := make([]string, len(vv))
copy(vv2, vv)
h2[k] = vv2
}
return h2
}
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
"Upgrade",
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
ctx := req.Context()
if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
outreq.Header = cloneHeader(req.Header)
// Modify for frp
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
p.Director(outreq)
outreq.Close = false
// Remove hop-by-hop headers listed in the "Connection" header.
// See RFC 2616, section 14.10.
if c := outreq.Header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
outreq.Header.Del(f)
}
}
}
// Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us.
for _, h := range hopHeaders {
if outreq.Header.Get(h) != "" {
outreq.Header.Del(h)
}
}
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
outreq.Header.Set("X-Forwarded-For", clientIP)
}
res, err := transport.RoundTrip(outreq)
if err != nil {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusNotFound)
rw.Write([]byte(NotFound))
return
}
// Remove hop-by-hop headers listed in the
// "Connection" header of the response.
if c := res.Header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
res.Header.Del(f)
}
}
}
for _, h := range hopHeaders {
res.Header.Del(h)
}
if p.ModifyResponse != nil {
if err := p.ModifyResponse(res); err != nil {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
return
}
}
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response,
// at least for *http.Transport. Build it up from Trailer.
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
p.copyResponse(rw, res.Body)
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
if p.FlushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: p.FlushInterval,
done: make(chan bool),
}
go mlw.flushLoop()
defer mlw.stop()
dst = mlw
}
}
var buf []byte
if p.BufferPool != nil {
buf = p.BufferPool.Get()
}
p.copyBuffer(dst, src, buf)
if p.BufferPool != nil {
p.BufferPool.Put(buf)
}
}
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 32*1024)
}
var written int64
for {
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
return written, rerr
}
}
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration
mu sync.Mutex // protects Write + Flush
done chan bool
}
func (m *maxLatencyWriter) Write(p []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.dst.Write(p)
}
func (m *maxLatencyWriter) flushLoop() {
t := time.NewTicker(m.latency)
defer t.Stop()
for {
select {
case <-m.done:
if onExitFlushLoop != nil {
onExitFlushLoop()
}
return
case <-t.C:
m.mu.Lock()
m.dst.Flush()
m.mu.Unlock()
}
}
}
func (m *maxLatencyWriter) stop() { m.done <- true }

View File

@@ -14,7 +14,8 @@ type VhostRouters struct {
type VhostRouter struct {
domain string
location string
listener *Listener
payload interface{}
}
func NewVhostRouters() *VhostRouters {
@@ -23,7 +24,7 @@ func NewVhostRouters() *VhostRouters {
}
}
func (r *VhostRouters) Add(domain, location string, l *Listener) {
func (r *VhostRouters) Add(domain, location string, payload interface{}) {
r.mutex.Lock()
defer r.mutex.Unlock()
@@ -35,7 +36,7 @@ func (r *VhostRouters) Add(domain, location string, l *Listener) {
vr := &VhostRouter{
domain: domain,
location: location,
listener: l,
payload: payload,
}
vrs = append(vrs, vr)

View File

@@ -18,6 +18,7 @@ import (
"sync"
"time"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
)
@@ -49,12 +50,16 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
return mux, nil
}
type CreateConnFunc func() (frpNet.Conn, error)
type VhostRouteConfig struct {
Domain string
Location string
RewriteHost string
Username string
Password string
CreateConnFn CreateConnFunc
}
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
@@ -90,7 +95,7 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
// if not exist, then check the wildcard_domain such as *.example.com
vr, found := v.registryRouter.Get(name, path)
if found {
return vr.listener, true
return vr.payload.(*Listener), true
}
domainSplit := strings.Split(name, ".")
@@ -105,7 +110,7 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
return
}
return vr.listener, true
return vr.payload.(*Listener), true
}
func (v *VhostMuxer) run() {
@@ -162,7 +167,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
c = sConn
l.Debug("get new http request host [%s] path [%s]", name, path)
l.accept <- c
err = errors.PanicToError(func() {
l.accept <- c
})
if err != nil {
l.Warn("listener is already closed, ignore this request")
}
}
type Listener struct {

119
vendor/github.com/armon/go-socks5/auth_test.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package socks5
import (
"bytes"
"testing"
)
func TestNoAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
s, _ := New(&Config{})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != NoAuth {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Valid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != UserPassAuth {
t.Fatal("Invalid Context Method")
}
val, ok := ctx.Payload["Username"]
if !ok {
t.Fatal("Missing key Username in auth context's payload")
}
if val != "foo" {
t.Fatal("Invalid Username in auth context's payload")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Invalid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != UserAuthFailed {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
t.Fatalf("bad: %v", out)
}
}
func TestNoSupportedAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != NoSupportedAuth {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
t.Fatalf("bad: %v", out)
}
}

24
vendor/github.com/armon/go-socks5/credentials_test.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
package socks5
import (
"testing"
)
func TestStaticCredentials(t *testing.T) {
creds := StaticCredentials{
"foo": "bar",
"baz": "",
}
if !creds.Valid("foo", "bar") {
t.Fatalf("expect valid")
}
if !creds.Valid("baz", "") {
t.Fatalf("expect valid")
}
if creds.Valid("foo", "") {
t.Fatalf("expect invalid")
}
}

169
vendor/github.com/armon/go-socks5/request_test.go generated vendored Normal file
View File

@@ -0,0 +1,169 @@
package socks5
import (
"bytes"
"encoding/binary"
"io"
"log"
"net"
"os"
"strings"
"testing"
)
type MockConn struct {
buf bytes.Buffer
}
func (m *MockConn) Write(b []byte) (int, error) {
return m.buf.Write(b)
}
func (m *MockConn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
}
func TestRequest_Connect(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitAll(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); err != nil {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
0,
0,
1,
127, 0, 0, 1,
0, 0,
'p', 'o', 'n', 'g',
}
// Ignore the port for both
out[8] = 0
out[9] = 0
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}
func TestRequest_Connect_RuleFail(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitNone(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
2,
0,
1,
0, 0, 0, 0,
0, 0,
}
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}

21
vendor/github.com/armon/go-socks5/resolver_test.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package socks5
import (
"testing"
"golang.org/x/net/context"
)
func TestDNSResolver(t *testing.T) {
d := DNSResolver{}
ctx := context.Background()
_, addr, err := d.Resolve(ctx, "localhost")
if err != nil {
t.Fatalf("err: %v", err)
}
if !addr.IsLoopback() {
t.Fatalf("expected loopback")
}
}

24
vendor/github.com/armon/go-socks5/ruleset_test.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
package socks5
import (
"testing"
"golang.org/x/net/context"
)
func TestPermitCommand(t *testing.T) {
ctx := context.Background()
r := &PermitCommand{true, false, false}
if _, ok := r.Allow(ctx, &Request{Command: ConnectCommand}); !ok {
t.Fatalf("expect connect")
}
if _, ok := r.Allow(ctx, &Request{Command: BindCommand}); ok {
t.Fatalf("do not expect bind")
}
if _, ok := r.Allow(ctx, &Request{Command: AssociateCommand}); ok {
t.Fatalf("do not expect associate")
}
}

110
vendor/github.com/armon/go-socks5/socks5_test.go generated vendored Normal file
View File

@@ -0,0 +1,110 @@
package socks5
import (
"bytes"
"encoding/binary"
"io"
"log"
"net"
"os"
"testing"
"time"
)
func TestSOCKS5_Connect(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Create a socks server
creds := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: creds}
conf := &Config{
AuthMethods: []Authenticator{cator},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}
serv, err := New(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
// Start listening
go func() {
if err := serv.ListenAndServe("tcp", "127.0.0.1:12365"); err != nil {
t.Fatalf("err: %v", err)
}
}()
time.Sleep(10 * time.Millisecond)
// Get a local conn
conn, err := net.Dial("tcp", "127.0.0.1:12365")
if err != nil {
t.Fatalf("err: %v", err)
}
// Connect, auth and connec to local
req := bytes.NewBuffer(nil)
req.Write([]byte{5})
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
req.Write(port)
// Send a ping
req.Write([]byte("ping"))
// Send all the bytes
conn.Write(req.Bytes())
// Verify response
expected := []byte{
socks5Version, UserPassAuth,
1, authSuccess,
5,
0,
0,
1,
127, 0, 0, 1,
0, 0,
'p', 'o', 'n', 'g',
}
out := make([]byte, len(expected))
conn.SetDeadline(time.Now().Add(time.Second))
if _, err := io.ReadAtLeast(conn, out, len(out)); err != nil {
t.Fatalf("err: %v", err)
}
// Ignore the port
out[12] = 0
out[13] = 0
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v", out)
}
}

22
vendor/github.com/davecgh/go-spew/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

14
vendor/github.com/davecgh/go-spew/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,14 @@
language: go
go:
- 1.5.4
- 1.6.3
- 1.7
install:
- go get -v golang.org/x/tools/cmd/cover
script:
- go test -v -tags=safe ./spew
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
after_success:
- go get -v github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin
- goveralls -coverprofile=profile.cov -service=travis-ci

205
vendor/github.com/davecgh/go-spew/README.md generated vendored Normal file
View File

@@ -0,0 +1,205 @@
go-spew
=======
[![Build Status](https://img.shields.io/travis/davecgh/go-spew.svg)]
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
(https://coveralls.io/r/davecgh/go-spew?branch=master)
Go-spew implements a deep pretty printer for Go data structures to aid in
debugging. A comprehensive suite of tests with 100% test coverage is provided
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
report. Go-spew is licensed under the liberal ISC license, so it may be used in
open source or commercial projects.
If you're interested in reading about how this package came to life and some
of the challenges involved in providing a deep pretty printer, there is a blog
post about it
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
## Documentation
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
(http://godoc.org/github.com/davecgh/go-spew/spew)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the excellent GoDoc site here:
http://godoc.org/github.com/davecgh/go-spew/spew
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
## Installation
```bash
$ go get -u github.com/davecgh/go-spew/spew
```
## Quick Start
Add this import line to the file you're working in:
```Go
import "github.com/davecgh/go-spew/spew"
```
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
```Go
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
```
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
and pointer addresses):
```Go
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
```
## Debugging a Web Application Example
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
```Go
package main
import (
"fmt"
"html"
"net/http"
"github.com/davecgh/go-spew/spew"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
```
## Sample Dump Output
```
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) {
(string) "one": (bool) true
}
}
([]uint8) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
```
## Sample Formatter Output
Double pointer to a uint8:
```
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
```
Pointer to circular struct with a uint8 field and a pointer to itself:
```
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
```
## Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available via the
spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
```
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables. This option
relies on access to the unsafe package, so it will not have any effect when
running in environments without access to the unsafe package such as Google
App Engine or with the "safe" build tag specified.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of capacities
for arrays, slices, maps and channels. This is useful when diffing data
structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are supported,
with other types sorted according to the reflect.Value.String() output
which guarantees display stability. Natural map order is used by
default.
* SpewKeys
SpewKeys specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only considered
if SortKeys is true.
```
## Unsafe Package Dependency
This package relies on the unsafe package to perform some of the more advanced
features, however it also supports a "limited" mode which allows it to work in
environments where the unsafe package is not available. By default, it will
operate in this mode on Google App Engine and when compiled with GopherJS. The
"safe" build tag may also be specified to force the package to build without
using the unsafe package.
## License
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.

22
vendor/github.com/davecgh/go-spew/cov_report.sh generated vendored Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
if ! type gocov >/dev/null 2>&1; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
# Only run the cgo tests if gcc is installed.
if type gcc >/dev/null 2>&1; then
(cd spew && gocov test -tags testcgo | gocov report)
else
(cd spew && gocov test | gocov report)
fi

298
vendor/github.com/davecgh/go-spew/spew/common_test.go generated vendored Normal file
View File

@@ -0,0 +1,298 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// custom type to test Stinger interface on non-pointer receiver.
type stringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with non-pointer receivers.
func (s stringer) String() string {
return "stringer " + string(s)
}
// custom type to test Stinger interface on pointer receiver.
type pstringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with only pointer receivers.
func (s *pstringer) String() string {
return "stringer " + string(*s)
}
// xref1 and xref2 are cross referencing structs for testing circular reference
// detection.
type xref1 struct {
ps2 *xref2
}
type xref2 struct {
ps1 *xref1
}
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
// reference for testing detection.
type indirCir1 struct {
ps2 *indirCir2
}
type indirCir2 struct {
ps3 *indirCir3
}
type indirCir3 struct {
ps1 *indirCir1
}
// embed is used to test embedded structures.
type embed struct {
a string
}
// embedwrap is used to test embedded structures.
type embedwrap struct {
*embed
e *embed
}
// panicer is used to intentionally cause a panic for testing spew properly
// handles them
type panicer int
func (p panicer) String() string {
panic("test panic")
}
// customError is used to test custom error interface invocation.
type customError int
func (e customError) Error() string {
return fmt.Sprintf("error: %d", int(e))
}
// stringizeWants converts a slice of wanted test output into a format suitable
// for a test error message.
func stringizeWants(wants []string) string {
s := ""
for i, want := range wants {
if i > 0 {
s += fmt.Sprintf("want%d: %s", i+1, want)
} else {
s += "want: " + want
}
}
return s
}
// testFailed returns whether or not a test failed by checking if the result
// of the test is in the slice of wanted strings.
func testFailed(result string, wants []string) bool {
for _, want := range wants {
if result == want {
return false
}
}
return true
}
type sortableStruct struct {
x int
}
func (ss sortableStruct) String() string {
return fmt.Sprintf("ss.%d", ss.x)
}
type unsortableStruct struct {
x int
}
type sortTestCase struct {
input []reflect.Value
expected []reflect.Value
}
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
getInterfaces := func(values []reflect.Value) []interface{} {
interfaces := []interface{}{}
for _, v := range values {
interfaces = append(interfaces, v.Interface())
}
return interfaces
}
for _, test := range tests {
spew.SortValues(test.input, cs)
// reflect.DeepEqual cannot really make sense of reflect.Value,
// probably because of all the pointer tricks. For instance,
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
// instead.
input := getInterfaces(test.input)
expected := getInterfaces(test.expected)
if !reflect.DeepEqual(input, expected) {
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
}
}
}
// TestSortValues ensures the sort functionality for relect.Value based sorting
// works as intended.
func TestSortValues(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
embedA := v(embed{"a"})
embedB := v(embed{"b"})
embedC := v(embed{"c"})
tests := []sortTestCase{
// No values.
{
[]reflect.Value{},
[]reflect.Value{},
},
// Bools.
{
[]reflect.Value{v(false), v(true), v(false)},
[]reflect.Value{v(false), v(false), v(true)},
},
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Uints.
{
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
},
// Floats.
{
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// Array
{
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
},
// Uintptrs.
{
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
},
// SortableStructs.
{
// Note: not sorted - DisableMethods is set.
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
// Invalid.
{
[]reflect.Value{embedB, embedA, embedC},
[]reflect.Value{embedB, embedA, embedC},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
// based sorting works as intended when using string methods.
func TestSortValuesWithMethods(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
// based sorting works as intended when using spew to stringify keys.
func TestSortValuesWithSpew(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
helpTestSortValues(tests, &cs, t)
}

1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

99
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This means the cgo tests are only added (and hence run) when
// specifially requested. This configuration is used because spew itself
// does not require cgo to run even though it does handle certain cgo types
// specially. Rather than forcing all clients to require cgo and an external
// C compiler just to run the tests, this scheme makes them optional.
// +build cgo,testcgo
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew/testdata"
)
func addCgoDumpTests() {
// C char pointer.
v := testdata.GetCgoCharPointer()
nv := testdata.GetCgoNullCharPointer()
pv := &v
vcAddr := fmt.Sprintf("%p", v)
vAddr := fmt.Sprintf("%p", pv)
pvAddr := fmt.Sprintf("%p", &pv)
vt := "*testdata._Ctype_char"
vs := "116"
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(nv, "("+vt+")(<nil>)\n")
// C char array.
v2, v2l, v2c := testdata.GetCgoCharArray()
v2Len := fmt.Sprintf("%d", v2l)
v2Cap := fmt.Sprintf("%d", v2c)
v2t := "[6]testdata._Ctype_char"
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
"{\n 00000000 74 65 73 74 32 00 " +
" |test2.|\n}"
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
// C unsigned char array.
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
v3Len := fmt.Sprintf("%d", v3l)
v3Cap := fmt.Sprintf("%d", v3c)
v3t := "[6]testdata._Ctype_unsignedchar"
v3t2 := "[6]testdata._Ctype_uchar"
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
"{\n 00000000 74 65 73 74 33 00 " +
" |test3.|\n}"
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
// C signed char array.
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
v4Len := fmt.Sprintf("%d", v4l)
v4Cap := fmt.Sprintf("%d", v4c)
v4t := "[6]testdata._Ctype_schar"
v4t2 := "testdata._Ctype_schar"
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
") 0\n}"
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
// C uint8_t array.
v5, v5l, v5c := testdata.GetCgoUint8tArray()
v5Len := fmt.Sprintf("%d", v5l)
v5Cap := fmt.Sprintf("%d", v5c)
v5t := "[6]testdata._Ctype_uint8_t"
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
"{\n 00000000 74 65 73 74 35 00 " +
" |test5.|\n}"
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
// C typedefed unsigned char array.
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
v6Len := fmt.Sprintf("%d", v6l)
v6Cap := fmt.Sprintf("%d", v6c)
v6t := "[6]testdata._Ctype_custom_uchar_t"
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
"{\n 00000000 74 65 73 74 36 00 " +
" |test6.|\n}"
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either cgo is not supported or "-tags testcgo" is not added to the go
// test command line. This file intentionally does not setup any cgo tests in
// this scenario.
// +build !cgo !testcgo
package spew_test
func addCgoDumpTests() {
// Don't add any tests for cgo since this file is only compiled when
// there should not be any cgo tests.
}

226
vendor/github.com/davecgh/go-spew/spew/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,226 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew"
)
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
// This example demonstrates how to use Dump to dump variables to stdout.
func ExampleDump() {
// The following package level declarations are assumed for this example:
/*
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
*/
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
f := Flag(5)
b := []byte{
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32,
}
// Dump!
spew.Dump(s1, f, b)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Flag) Unknown flag (5)
// ([]uint8) (len=34 cap=34) {
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
// 00000020 31 32 |12|
// }
//
}
// This example demonstrates how to use Printf to display a variable with a
// format string and inline formatting.
func ExamplePrintf() {
// Create a double pointer to a uint 8.
ui8 := uint8(5)
pui8 := &ui8
ppui8 := &pui8
// Create a circular data type.
type circular struct {
ui8 uint8
c *circular
}
c := circular{ui8: 1}
c.c = &c
// Print!
spew.Printf("ppui8: %v\n", ppui8)
spew.Printf("circular: %v\n", c)
// Output:
// ppui8: <**>5
// circular: {1 <*>{1 <*><shown>}}
}
// This example demonstrates how to use a ConfigState.
func ExampleConfigState() {
// Modify the indent level of the ConfigState only. The global
// configuration is not modified.
scs := spew.ConfigState{Indent: "\t"}
// Output using the ConfigState instance.
v := map[string]int{"one": 1}
scs.Printf("v: %v\n", v)
scs.Dump(v)
// Output:
// v: map[one:1]
// (map[string]int) (len=1) {
// (string) (len=3) "one": (int) 1
// }
}
// This example demonstrates how to use ConfigState.Dump to dump variables to
// stdout
func ExampleConfigState_Dump() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances with different indentation.
scs := spew.ConfigState{Indent: "\t"}
scs2 := spew.ConfigState{Indent: " "}
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
// Dump using the ConfigState instances.
scs.Dump(s1)
scs2.Dump(s1)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
//
}
// This example demonstrates how to use ConfigState.Printf to display a variable
// with a format string and inline formatting.
func ExampleConfigState_Printf() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances and modify the method handling of the
// first ConfigState only.
scs := spew.NewDefaultConfig()
scs2 := spew.NewDefaultConfig()
scs.DisableMethods = true
// Alternatively
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
// scs2 := spew.ConfigState{Indent: " "}
// This is of type Flag which implements a Stringer and has raw value 1.
f := flagTwo
// Dump using the ConfigState instances.
scs.Printf("f: %v\n", f)
scs2.Printf("f: %v\n", f)
// Output:
// f: 1
// f: flagTwo
}

1558
vendor/github.com/davecgh/go-spew/spew/format_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
)
// dummyFmtState implements a fake fmt.State to use for testing invalid
// reflect.Value handling. This is necessary because the fmt package catches
// invalid values before invoking the formatter on them.
type dummyFmtState struct {
bytes.Buffer
}
func (dfs *dummyFmtState) Flag(f int) bool {
if f == int('+') {
return true
}
return false
}
func (dfs *dummyFmtState) Precision() (int, bool) {
return 0, false
}
func (dfs *dummyFmtState) Width() (int, bool) {
return 0, false
}
// TestInvalidReflectValue ensures the dump and formatter code handles an
// invalid reflect value properly. This needs access to internal state since it
// should never happen in real code and therefore can't be tested via the public
// API.
func TestInvalidReflectValue(t *testing.T) {
i := 1
// Dump invalid reflect value.
v := new(reflect.Value)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(*v)
s := buf.String()
want := "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter invalid reflect value.
buf2 := new(dummyFmtState)
f := formatState{value: *v, cs: &Config, fs: buf2}
f.format(*v)
s = buf2.String()
want = "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
}
}
// SortValues makes the internal sortValues function available to the test
// package.
func SortValues(values []reflect.Value, cs *ConfigState) {
sortValues(values, cs)
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
"unsafe"
)
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
// the maximum kind value which does not exist. This is needed to test the
// fallback code which punts to the standard fmt library for new types that
// might get added to the language.
func changeKind(v *reflect.Value, readOnly bool) {
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
if readOnly {
*rvf |= flagRO
} else {
*rvf &= ^uintptr(flagRO)
}
}
// TestAddedReflectValue tests functionaly of the dump and formatter code which
// falls back to the standard fmt library for new types that might get added to
// the language.
func TestAddedReflectValue(t *testing.T) {
i := 1
// Dump using a reflect.Value that is exported.
v := reflect.ValueOf(int8(5))
changeKind(&v, false)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(v)
s := buf.String()
want := "(int8) 5"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Dump using a reflect.Value that is not exported.
changeKind(&v, true)
buf.Reset()
d.dump(v)
s = buf.String()
want = "(int8) <int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is exported.
changeKind(&v, false)
buf2 := new(dummyFmtState)
f := formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "5"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is not exported.
changeKind(&v, true)
buf2.Reset()
f = formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "<int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
}

320
vendor/github.com/davecgh/go-spew/spew/spew_test.go generated vendored Normal file
View File

@@ -0,0 +1,320 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/davecgh/go-spew/spew"
)
// spewFunc is used to identify which public function of the spew package or
// ConfigState a test applies to.
type spewFunc int
const (
fCSFdump spewFunc = iota
fCSFprint
fCSFprintf
fCSFprintln
fCSPrint
fCSPrintln
fCSSdump
fCSSprint
fCSSprintf
fCSSprintln
fCSErrorf
fCSNewFormatter
fErrorf
fFprint
fFprintln
fPrint
fPrintln
fSdump
fSprint
fSprintf
fSprintln
)
// Map of spewFunc values to names for pretty printing.
var spewFuncStrings = map[spewFunc]string{
fCSFdump: "ConfigState.Fdump",
fCSFprint: "ConfigState.Fprint",
fCSFprintf: "ConfigState.Fprintf",
fCSFprintln: "ConfigState.Fprintln",
fCSSdump: "ConfigState.Sdump",
fCSPrint: "ConfigState.Print",
fCSPrintln: "ConfigState.Println",
fCSSprint: "ConfigState.Sprint",
fCSSprintf: "ConfigState.Sprintf",
fCSSprintln: "ConfigState.Sprintln",
fCSErrorf: "ConfigState.Errorf",
fCSNewFormatter: "ConfigState.NewFormatter",
fErrorf: "spew.Errorf",
fFprint: "spew.Fprint",
fFprintln: "spew.Fprintln",
fPrint: "spew.Print",
fPrintln: "spew.Println",
fSdump: "spew.Sdump",
fSprint: "spew.Sprint",
fSprintf: "spew.Sprintf",
fSprintln: "spew.Sprintln",
}
func (f spewFunc) String() string {
if s, ok := spewFuncStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
}
// spewTest is used to describe a test to be performed against the public
// functions of the spew package or ConfigState.
type spewTest struct {
cs *spew.ConfigState
f spewFunc
format string
in interface{}
want string
}
// spewTests houses the tests to be performed against the public functions of
// the spew package and ConfigState.
//
// These tests are only intended to ensure the public functions are exercised
// and are intentionally not exhaustive of types. The exhaustive type
// tests are handled in the dump and format tests.
var spewTests []spewTest
// redirStdout is a helper function to return the standard output from f as a
// byte slice.
func redirStdout(f func()) ([]byte, error) {
tempFile, err := ioutil.TempFile("", "ss-test")
if err != nil {
return nil, err
}
fileName := tempFile.Name()
defer os.Remove(fileName) // Ignore error
origStdout := os.Stdout
os.Stdout = tempFile
f()
os.Stdout = origStdout
tempFile.Close()
return ioutil.ReadFile(fileName)
}
func initSpewTests() {
// Config states with various settings.
scsDefault := spew.NewDefaultConfig()
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
scsNoCap := &spew.ConfigState{DisableCapacities: true}
// Variables for tests on types which implement Stringer interface with and
// without a pointer receiver.
ts := stringer("test")
tps := pstringer("test")
type ptrTester struct {
s *struct{}
}
tptr := &ptrTester{s: &struct{}{}}
// depthTester is used to test max depth handling for structs, array, slices
// and maps.
type depthTester struct {
ic indirCir1
arr [1]string
slice []string
m map[string]int
}
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
map[string]int{"one": 1}}
// Variable for tests on types which implement error interface.
te := customError(10)
spewTests = []spewTest{
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
{scsDefault, fCSFprint, "", int16(32767), "32767"},
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
{scsDefault, fFprint, "", float32(3.14), "3.14"},
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
{scsDefault, fPrint, "", true, "true"},
{scsDefault, fPrintln, "", false, "false\n"},
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
{scsNoMethods, fCSFprint, "", ts, "test"},
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
{scsNoMethods, fCSFprint, "", tps, "test"},
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
{scsNoPmethods, fCSFprint, "", tps, "test"},
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
"(len=4) (stringer test) \"test\"\n"},
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
"(error: 10) 10\n"},
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
}
}
// TestSpew executes all of the tests described by spewTests.
func TestSpew(t *testing.T) {
initSpewTests()
t.Logf("Running %d tests", len(spewTests))
for i, test := range spewTests {
buf := new(bytes.Buffer)
switch test.f {
case fCSFdump:
test.cs.Fdump(buf, test.in)
case fCSFprint:
test.cs.Fprint(buf, test.in)
case fCSFprintf:
test.cs.Fprintf(buf, test.format, test.in)
case fCSFprintln:
test.cs.Fprintln(buf, test.in)
case fCSPrint:
b, err := redirStdout(func() { test.cs.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSPrintln:
b, err := redirStdout(func() { test.cs.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSSdump:
str := test.cs.Sdump(test.in)
buf.WriteString(str)
case fCSSprint:
str := test.cs.Sprint(test.in)
buf.WriteString(str)
case fCSSprintf:
str := test.cs.Sprintf(test.format, test.in)
buf.WriteString(str)
case fCSSprintln:
str := test.cs.Sprintln(test.in)
buf.WriteString(str)
case fCSErrorf:
err := test.cs.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fCSNewFormatter:
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
case fErrorf:
err := spew.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fFprint:
spew.Fprint(buf, test.in)
case fFprintln:
spew.Fprintln(buf, test.in)
case fPrint:
b, err := redirStdout(func() { spew.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fPrintln:
b, err := redirStdout(func() { spew.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fSdump:
str := spew.Sdump(test.in)
buf.WriteString(str)
case fSprint:
str := spew.Sprint(test.in)
buf.WriteString(str)
case fSprintf:
str := spew.Sprintf(test.format, test.in)
buf.WriteString(str)
case fSprintln:
str := spew.Sprintln(test.in)
buf.WriteString(str)
default:
t.Errorf("%v #%d unrecognized function", test.f, i)
continue
}
s := buf.String()
if test.want != s {
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
continue
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This code should really only be in the dumpcgo_test.go file,
// but unfortunately Go will not allow cgo in test files, so this is a
// workaround to allow cgo types to be tested. This configuration is used
// because spew itself does not require cgo to run even though it does handle
// certain cgo types specially. Rather than forcing all clients to require cgo
// and an external C compiler just to run the tests, this scheme makes them
// optional.
// +build cgo,testcgo
package testdata
/*
#include <stdint.h>
typedef unsigned char custom_uchar_t;
char *ncp = 0;
char *cp = "test";
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
*/
import "C"
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
// used for tests.
func GetCgoNullCharPointer() interface{} {
return C.ncp
}
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
// tests.
func GetCgoCharPointer() interface{} {
return C.cp
}
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
// This is only used for tests.
func GetCgoCharArray() (interface{}, int, int) {
return C.ca, len(C.ca), cap(C.ca)
}
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
// array's len and cap. This is only used for tests.
func GetCgoUnsignedCharArray() (interface{}, int, int) {
return C.uca, len(C.uca), cap(C.uca)
}
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
// and cap. This is only used for tests.
func GetCgoSignedCharArray() (interface{}, int, int) {
return C.sca, len(C.sca), cap(C.sca)
}
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
// cap. This is only used for tests.
func GetCgoUint8tArray() (interface{}, int, int) {
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
}
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
// cgo and the array's len and cap. This is only used for tests.
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
return C.tuca, len(C.tuca), cap(C.tuca)
}

61
vendor/github.com/davecgh/go-spew/test_coverage.txt generated vendored Normal file
View File

@@ -0,0 +1,61 @@
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)

1536
vendor/github.com/docopt/docopt-go/docopt_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

37
vendor/github.com/docopt/docopt-go/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
package docopt
import (
"fmt"
"sort"
)
func ExampleParse() {
usage := `Usage:
config_example tcp [<host>] [--force] [--timeout=<seconds>]
config_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
config_example -h | --help | --version`
// parse the command line `comfig_example tcp 127.0.0.1 --force`
argv := []string{"tcp", "127.0.0.1", "--force"}
arguments, _ := Parse(usage, argv, true, "0.1.1rc", false)
// sort the keys of the arguments map
var keys []string
for k := range arguments {
keys = append(keys, k)
}
sort.Strings(keys)
// print the argument keys and values
for _, k := range keys {
fmt.Printf("%9s %v\n", k, arguments[k])
}
// output:
// --baud <nil>
// --force true
// --help false
// --timeout <nil>
// --version false
// -h false
// <host> 127.0.0.1
// <port> <nil>
// serial false
// tcp true
}

View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Usage: arguments_example [-vqrh] [FILE] ...
arguments_example (--left | --right) CORRECTION FILE
Process FILE and optionally apply correction to either left-hand side or
right-hand side.
Arguments:
FILE optional input file
CORRECTION correction angle, needs FILE, --left or --right to be present
Options:
-h --help
-v verbose mode
-q quiet mode
-r make report
--left use left-hand side
--right use right-hand side`
arguments, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(arguments)
}

View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Not a serious example.
Usage:
calculator_example <value> ( ( + | - | * | / ) <value> )...
calculator_example <function> <value> [( , <value> )]...
calculator_example (-h | --help)
Examples:
calculator_example 1 + 2 + 3 + 4 + 5
calculator_example 1 + 2 '*' 3 / 4 - 5 # note quotes around '*'
calculator_example sum 10 , 20 , 30 , 40
Options:
-h, --help
`
arguments, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(arguments)
}

View File

@@ -0,0 +1,76 @@
package main
import (
"encoding/json"
"fmt"
"github.com/docopt/docopt-go"
"strings"
)
func loadJSONConfig() map[string]interface{} {
var result map[string]interface{}
jsonData := []byte(`{"--force": true, "--timeout": "10", "--baud": "9600"}`)
json.Unmarshal(jsonData, &result)
return result
}
func loadIniConfig() map[string]interface{} {
iniData := `
[default-arguments]
--force
--baud=19200
<host>=localhost`
// trivial ini parser
// default value for an item is bool: true (for --force)
// otherwise the value is a string
iniParsed := make(map[string]map[string]interface{})
var section string
for _, line := range strings.Split(iniData, "\n") {
if strings.HasPrefix(line, "[") {
section = line
iniParsed[section] = make(map[string]interface{})
} else if section != "" {
kv := strings.SplitN(line, "=", 2)
if len(kv) == 1 {
iniParsed[section][kv[0]] = true
} else if len(kv) == 2 {
iniParsed[section][kv[0]] = kv[1]
}
}
}
return iniParsed["[default-arguments]"]
}
// merge combines two maps.
// truthiness takes priority over falsiness
// mapA takes priority over mapB
func merge(mapA, mapB map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range mapA {
result[k] = v
}
for k, v := range mapB {
if _, ok := result[k]; !ok || result[k] == nil || result[k] == false {
result[k] = v
}
}
return result
}
func main() {
usage := `Usage:
config_file_example tcp [<host>] [--force] [--timeout=<seconds>]
config_file_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
config_file_example -h | --help | --version`
jsonConfig := loadJSONConfig()
iniConfig := loadIniConfig()
arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
// Arguments take priority over INI, INI takes priority over JSON
result := merge(arguments, merge(iniConfig, jsonConfig))
fmt.Println("JSON config: ", jsonConfig)
fmt.Println("INI config: ", iniConfig)
fmt.Println("Result: ", result)
}

View File

@@ -0,0 +1,22 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Usage: counted_example --help
counted_example -v...
counted_example go [go]
counted_example (--path=<path>)...
counted_example <file> <file>
Try: counted_example -vvvvvvvvvv
counted_example go go
counted_example --path ./here --path ./there
counted_example this.txt that.txt`
arguments, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(arguments)
}

View File

@@ -0,0 +1,38 @@
package git
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: git branch [options] [-r | -a] [--merged=<commit> | --no-merged=<commit>]
git branch [options] [-l] [-f] <branchname> [<start-point>]
git branch [options] [-r] (-d | -D) <branchname>
git branch [options] (-m | -M) [<oldbranch>] <newbranch>
Generic options:
-h, --help
-v, --verbose show hash and subject, give twice for upstream branch
-t, --track set up tracking mode (see git-pull(1))
--set-upstream change upstream info
--color=<when> use colored output
-r act on remote-tracking branches
--contains=<commit> print only branches that contain the commit
--abbrev=<n> use <n> digits to display SHA-1s
Specific git-branch actions:
-a list both remote-tracking and local branches
-d delete fully merged branch
-D delete branch (even if not merged)
-m move/rename a branch and its reflog
-M move/rename a branch, even if target exists
-l create the branch's reflog
-f, --force force creation (when already exists)
--no-merged=<commit> print only not merged branches
--merged=<commit> print only merged branches
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
}

View File

@@ -0,0 +1,30 @@
package git
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: git checkout [options] <branch>
git checkout [options] <branch> -- <file>...
options:
-q, --quiet suppress progress reporting
-b <branch> create and checkout a new branch
-B <branch> create/reset and checkout a branch
-l create reflog for new branch
-t, --track set upstream info for new branch
--orphan <new branch>
new unparented branch
-2, --ours checkout our version for unmerged files
-3, --theirs checkout their version for unmerged files
-f, --force force checkout (throw away local modifications)
-m, --merge perform a 3-way merge with the new branch
--conflict <style> conflict style (merge or diff3)
-p, --patch select hunks interactively
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
}

View File

@@ -0,0 +1,37 @@
package git
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: git clone [options] [--] <repo> [<dir>]
options:
-v, --verbose be more verbose
-q, --quiet be more quiet
--progress force progress reporting
-n, --no-checkout don't create a checkout
--bare create a bare repository
--mirror create a mirror repository (implies bare)
-l, --local to clone from a local repository
--no-hardlinks don't use local hardlinks, always copy
-s, --shared setup as shared repository
--recursive initialize submodules in the clone
--recurse-submodules initialize submodules in the clone
--template <template-directory>
directory from which templates will be used
--reference <repo> reference repository
-o, --origin <branch>
use <branch> instead of 'origin' to track upstream
-b, --branch <branch>
checkout <branch> instead of the remote's HEAD
-u, --upload-pack <path>
path to git-upload-pack on the remote
--depth <depth> create a shallow clone of that depth
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
}

108
vendor/github.com/docopt/docopt-go/examples/git/git.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
"os"
"os/exec"
)
func main() {
usage := `usage: git [--version] [--exec-path=<path>] [--html-path]
[-p|--paginate|--no-pager] [--no-replace-objects]
[--bare] [--git-dir=<path>] [--work-tree=<path>]
[-c <name>=<value>] [--help]
<command> [<args>...]
options:
-c <name=value>
-h, --help
-p, --paginate
The most commonly used git commands are:
add Add file contents to the index
branch List, create, or delete branches
checkout Checkout a branch or paths to the working tree
clone Clone a repository into a new directory
commit Record changes to the repository
push Update remote refs along with associated objects
remote Manage set of tracked repositories
See 'git help <command>' for more information on a specific command.
`
args, _ := docopt.Parse(usage, nil, true, "git version 1.7.4.4", true)
fmt.Println("global arguments:")
fmt.Println(args)
fmt.Println("command arguments:")
cmd := args["<command>"].(string)
cmdArgs := args["<args>"].([]string)
err := runCommand(cmd, cmdArgs)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func goRun(scriptName string, args []string) (err error) {
cmdArgs := make([]string, 2)
cmdArgs[0] = "run"
cmdArgs[1] = scriptName
cmdArgs = append(cmdArgs, args...)
osCmd := exec.Command("go", cmdArgs...)
var out []byte
out, err = osCmd.Output()
fmt.Println(string(out))
if err != nil {
return
}
return
}
func runCommand(cmd string, args []string) (err error) {
argv := make([]string, 1)
argv[0] = cmd
argv = append(argv, args...)
switch cmd {
case "add":
// subcommand is a function call
return cmdAdd(argv)
case "branch":
// subcommand is a script
return goRun("branch/git_branch.go", argv)
case "checkout", "clone", "commit", "push", "remote":
// subcommand is a script
scriptName := fmt.Sprintf("%s/git_%s.go", cmd, cmd)
return goRun(scriptName, argv)
case "help", "":
return goRun("git.go", []string{"git_add.go", "--help"})
}
return fmt.Errorf("%s is not a git command. See 'git help'", cmd)
}
func cmdAdd(argv []string) (err error) {
usage := `usage: git add [options] [--] [<filepattern>...]
options:
-h, --help
-n, --dry-run dry run
-v, --verbose be verbose
-i, --interactive interactive picking
-p, --patch select hunks interactively
-e, --edit edit current diff and apply
-f, --force allow adding otherwise ignored files
-u, --update update tracked files
-N, --intent-to-add record only the fact that the path will be added later
-A, --all add all, noticing removal of tracked files
--refresh don't add, only refresh the index
--ignore-errors just skip files which cannot be added because of errors
--ignore-missing check if - even missing - files are ignored in dry run
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
return
}

View File

@@ -0,0 +1,34 @@
package git
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: git push [options] [<repository> [<refspec>...]]
options:
-h, --help
-v, --verbose be more verbose
-q, --quiet be more quiet
--repo <repository> repository
--all push all refs
--mirror mirror all refs
--delete delete refs
--tags push tags (can't be used with --all or --mirror)
-n, --dry-run dry run
--porcelain machine-readable output
-f, --force force updates
--thin use thin pack
--receive-pack <receive-pack>
receive pack program
--exec <receive-pack>
receive pack program
-u, --set-upstream set upstream for git pull/status
--progress force progress reporting
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
}

View File

@@ -0,0 +1,28 @@
package git
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: git remote [-v | --verbose]
git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
git remote rename <old> <new>
git remote rm <name>
git remote set-head <name> (-a | -d | <branch>)
git remote [-v | --verbose] show [-n] <name>
git remote prune [-n | --dry-run] <name>
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
git remote set-branches <name> [--add] <branch>...
git remote set-url <name> <newurl> [<oldurl>]
git remote set-url --add <name> <newurl>
git remote set-url --delete <name> <url>
options:
-v, --verbose be verbose; must be placed before a subcommand
`
args, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(args)
}

View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Naval Fate.
Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> [--speed=<kn>]
naval_fate ship shoot <x> <y>
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
naval_fate -h | --help
naval_fate --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.`
arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
fmt.Println(arguments)
}

View File

@@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Usage: odd_even_example [-h | --help] (ODD EVEN)...
Example, try:
odd_even_example 1 2 3 4
Options:
-h, --help`
arguments, _ := docopt.Parse(usage, nil, true, "", false)
fmt.Println(arguments)
}

View File

@@ -0,0 +1,43 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Example of program with many options using docopt.
Usage:
options_example [-hvqrf NAME] [--exclude=PATTERNS]
[--select=ERRORS | --ignore=ERRORS] [--show-source]
[--statistics] [--count] [--benchmark] PATH...
options_example (--doctest | --testsuite=DIR)
options_example --version
Arguments:
PATH destination path
Options:
-h --help show this help message and exit
--version show version and exit
-v --verbose print status messages
-q --quiet report only file names
-r --repeat show all occurrences of the same error
--exclude=PATTERNS exclude files or directories which match these comma
separated patterns [default: .svn,CVS,.bzr,.hg,.git]
-f NAME --file=NAME when parsing directories, only check filenames matching
these comma separated patterns [default: *.go]
--select=ERRORS select errors and warnings (e.g. E,W6)
--ignore=ERRORS skip errors and warnings (e.g. E4,W)
--show-source show source code for each error
--statistics count errors and warnings
--count print total number of errors and warnings to standard
error and set exit code to 1 if total is not null
--benchmark measure processing speed
--testsuite=DIR run regression tests from dir
--doctest run doctest on myself`
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
fmt.Println(arguments)
}

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