Compare commits

...

45 Commits

Author SHA1 Message Date
fatedier
c008b14d0f Merge pull request #735 from fatedier/dev
bump version to v0.18.0
2018-05-02 22:37:09 +08:00
fatedier
853892f3cd change version to v0.18.0 2018-05-02 22:09:15 +08:00
fatedier
e43f9f5850 Merge pull request #734 from fatedier/mux
use yamux instead of smux
2018-05-02 21:57:18 +08:00
fatedier
d5f30ccd6b Merge pull request #726 from shuaihanhungry/develop
do not ignore config parsing error
2018-05-02 21:51:02 +08:00
hanshuai
b87df569e7 do not ignore config parsing error 2018-05-02 20:40:33 +08:00
fatedier
976cf3e9f8 use yamux instead of smux 2018-04-25 02:42:00 +08:00
fatedier
371c401f5b Merge pull request #720 from fatedier/dev
bump version to v0.17.0
2018-04-24 01:58:57 +08:00
fatedier
69919e8ef9 Merge pull request #719 from fatedier/doc
update
2018-04-24 01:55:19 +08:00
fatedier
9abbe33790 typo 2018-04-24 01:51:52 +08:00
fatedier
4a5c00286e doc: update 2018-04-24 01:28:25 +08:00
fatedier
dfb892c8f6 Merge pull request #718 from fatedier/cmd
more cmds
2018-04-23 03:07:14 +08:00
fatedier
461c4c18fd update doc 2018-04-23 03:04:33 +08:00
fatedier
00b9ba95ae frpc: support specify default dns server, close #700 2018-04-23 02:59:40 +08:00
fatedier
c47aad348d fix 2018-04-23 02:40:25 +08:00
fatedier
4cb4da3afc add package github.com/spf13/cobra 2018-04-23 02:35:50 +08:00
fatedier
c1f57da00d update packages 2018-04-23 02:31:00 +08:00
fatedier
fe187eb8ec remove package github.com/docopt/docopt-go 2018-04-23 02:15:01 +08:00
fatedier
0f6f674a64 cmd: support more cli command 2018-04-23 02:00:25 +08:00
fatedier
814afbe1f6 Merge pull request #688 from miwee/dashboard_api_client_status
dashboard_api for getting a client status by name
2018-04-10 17:57:26 +08:00
miwee
3fde9176c9 dashboard_api for getting a client status by name 2018-04-04 12:07:20 +05:30
fatedier
af7cca1a93 Merge pull request #685 from toby1991/dev
fix https://github.com/fatedier/frp/issues/684
2018-04-04 11:32:48 +08:00
toby1991
7dd28a14aa fix https://github.com/fatedier/frp/issues/684
#684 Cannot build from Dockerfile
2018-04-04 11:06:47 +08:00
fatedier
1325c59a4c Merge pull request #672 from fatedier/dev
bump version to v0.16.1
2018-03-21 18:09:39 +08:00
fatedier
82dc1e924f vhost: typo fix 2018-03-21 18:06:43 +08:00
fatedier
3166bdf3f0 bump version to v0.16.1 2018-03-21 18:00:31 +08:00
fatedier
8af70c8822 update go version to go1.10 2018-03-21 11:52:11 +08:00
fatedier
87763e8251 Merge pull request #670 from fatedier/new
some fix
2018-03-21 11:45:48 +08:00
fatedier
e9241aeb94 udp proxy: fix #652 2018-03-19 20:22:15 +08:00
fatedier
2eaf134042 Merge pull request #646 from travisghansen/dev
build freebsd packages
2018-02-27 23:14:15 +08:00
Travis Glenn Hansen
1739e012d6 build freebsd packages 2018-02-26 21:00:55 -07:00
fatedier
9e8980429f typo 2018-02-07 11:39:30 +08:00
fatedier
1d0865ca49 statsConn: avoid repetition of close function 2018-02-01 11:15:35 +08:00
fatedier
5c9909aeef typo 2018-01-30 22:07:16 +08:00
fatedier
456ce09061 Merge pull request #630 from fatedier/dev
bump version to v0.16.0
2018-01-30 00:04:02 +08:00
fatedier
ffc13b704a update version 2018-01-30 00:00:05 +08:00
fatedier
5d239127bb Merge pull request #629 from fatedier/new
new feature
2018-01-29 23:58:55 +08:00
fatedier
9b990adf96 frpc: add proxy status 'wait start' 2018-01-29 23:51:46 +08:00
fatedier
44e8108910 ci: add test case for range ports mapping 2018-01-29 23:13:10 +08:00
fatedier
1c35e9a0c6 doc: update 2018-01-29 23:05:17 +08:00
fatedier
8e719ff0ff frps: new params max_ports_per_client 2018-01-26 14:56:55 +08:00
fatedier
637ddbce1f frpc: udpate proxies check and start logic 2018-01-26 00:23:48 +08:00
fatedier
ce8fde793c new feature: range section for mapping range ports 2018-01-25 23:05:07 +08:00
fatedier
eede31c064 doc: about static_file plugin 2018-01-24 23:27:03 +08:00
fatedier
41c41789b6 plugin: socks5 support user password auth, close #484 2018-01-24 23:06:38 +08:00
fatedier
68dfc89bce plugin: new plugin static_file for getting files by http protocol 2018-01-24 17:49:13 +08:00
256 changed files with 26054 additions and 12192 deletions

View File

@@ -2,8 +2,7 @@ sudo: false
language: go language: go
go: go:
- 1.8.x - 1.10.x
- 1.9.x
install: install:
- make - make

View File

@@ -1,4 +1,4 @@
FROM golang:1.8 FROM golang:1.10
COPY . /go/src/github.com/fatedier/frp COPY . /go/src/github.com/fatedier/frp

View File

@@ -15,12 +15,7 @@ file:
go generate ./assets/... go generate ./assets/...
fmt: fmt:
go fmt ./assets/... go fmt ./...
go fmt ./client/...
go fmt ./cmd/...
go fmt ./models/...
go fmt ./server/...
go fmt ./utils/...
frps: frps:
go build -o bin/frps ./cmd/frps go build -o bin/frps ./cmd/frps
@@ -44,7 +39,7 @@ ci:
go test -v ./tests/... go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd - cd ./tests && ./clean_test.sh && cd -
ciclean: cic:
cd ./tests && ./clean_test.sh && cd - cd ./tests && ./clean_test.sh && cd -
alltest: gotest ci alltest: gotest ci
@@ -53,6 +48,3 @@ clean:
rm -f ./bin/frpc rm -f ./bin/frpc
rm -f ./bin/frps rm -f ./bin/frps
cd ./tests && ./clean_test.sh && cd - cd ./tests && ./clean_test.sh && cd -
save:
godep save ./...

View File

@@ -9,6 +9,10 @@ build: app
app: app:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
@@ -23,10 +27,10 @@ app:
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
temp: temp:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps

View File

@@ -20,6 +20,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains) * [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
* [Forward DNS query request](#forward-dns-query-request) * [Forward DNS query request](#forward-dns-query-request)
* [Forward unix domain socket](#forward-unix-domain-socket) * [Forward unix domain socket](#forward-unix-domain-socket)
* [Expose a simple http file server](#expose-a-simple-http-file-server)
* [Expose your service in security](#expose-your-service-in-security) * [Expose your service in security](#expose-your-service-in-security)
* [P2P Mode](#p2p-mode) * [P2P Mode](#p2p-mode)
* [Connect website through frpc's network](#connect-website-through-frpcs-network) * [Connect website through frpc's network](#connect-website-through-frpcs-network)
@@ -30,8 +31,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Encryption and Compression](#encryption-and-compression) * [Encryption and Compression](#encryption-and-compression)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Get proxy status from client](#get-proxy-status-from-client) * [Get proxy status from client](#get-proxy-status-from-client)
* [Privilege Mode](#privilege-mode) * [Port White List](#port-white-list)
* [Port White List](#port-white-list)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing) * [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Support KCP Protocol](#support-kcp-protocol) * [Support KCP Protocol](#support-kcp-protocol)
* [Connection Pool](#connection-pool) * [Connection Pool](#connection-pool)
@@ -41,6 +41,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Custom subdomain names](#custom-subdomain-names) * [Custom subdomain names](#custom-subdomain-names)
* [URL routing](#url-routing) * [URL routing](#url-routing)
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy) * [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
* [Range ports mapping](#range-ports-mapping)
* [Plugin](#plugin) * [Plugin](#plugin)
* [Development Plan](#development-plan) * [Development Plan](#development-plan)
* [Contributing](#contributing) * [Contributing](#contributing)
@@ -214,6 +215,32 @@ Configure frps same as above.
`curl http://x.x.x.x:6000/version` `curl http://x.x.x.x:6000/version`
### Expose a simple http file server
A simple way to visit files in the LAN.
Configure frps same as above.
1. Start frpc with configurations:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
plugin_local_path = /tmp/file
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
```
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
### Expose your service in security ### Expose your service in security
For some services, if expose them to the public network directly will be a security risk. For some services, if expose them to the public network directly will be a security risk.
@@ -356,7 +383,7 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
### Authentication ### Authentication
Since v0.10.0, you only need to set `privilege_token` in frps.ini and frpc.ini. Since v0.10.0, you only need to set `token` in frps.ini and frpc.ini.
Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication. Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
@@ -395,21 +422,17 @@ Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to le
Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file. 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 ### Port White List
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client. `allow_ports` in frps.ini is used for preventing abuse of ports:
#### Port White List
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
```ini ```ini
# frps.ini # frps.ini
[common] [common]
privilege_allow_ports = 2000-3000,3001,3003,4000-50000 allow_ports = 2000-3000,3001,3003,4000-50000
``` ```
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`. `allow_ports` consists of a specific port or a range of ports divided by `,`.
### TCP Stream Multiplexing ### TCP Stream Multiplexing
@@ -512,7 +535,7 @@ type = http
local_port = 80 local_port = 80
custom_domains = test.yourdomain.com custom_domains = test.yourdomain.com
http_user = abc http_user = abc
http_pwd = abc http_passwd = abc
``` ```
Visit `http://test.yourdomain.com` and now you need to input username and password. Visit `http://test.yourdomain.com` and now you need to input username and password.
@@ -576,11 +599,26 @@ server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080 http_proxy = http://user:pwd@192.168.1.128:8080
``` ```
### Range ports mapping
Proxy name has prefix `range:` will support mapping range ports.
```ini
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
```
frpc will generate 6 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_5`.
### Plugin ### Plugin
frpc only forward request to local tcp or udp port by default. frpc only forward request to local tcp or udp port by default.
Plugin is used for providing rich features. There are built-in plugins such as **unix_domain_socket**, **http_proxy**, **socks5** and you can see [example usage](#example-usage). Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin. Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
@@ -598,17 +636,13 @@ plugin_http_passwd = abc
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin. `plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
## Development Plan ## Development Plan
* Log http request information in frps. * Log http request information in frps.
* Direct reverse proxy, like haproxy. * Direct reverse proxy, like haproxy.
* Load balance to different service in frpc. * Load balance to different service in frpc.
* Frpc can directly be a webserver for static files.
* P2p communicate by making udp hole to penetrate NAT.
* kubernetes ingress support. * kubernetes ingress support.
## Contributing ## Contributing
Interested in getting involved? We would like to help you! Interested in getting involved? We would like to help you!

View File

@@ -18,6 +18,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务) * [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求) * [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字) * [转发 Unix域套接字](#转发-unix域套接字)
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
* [安全地暴露内网服务](#安全地暴露内网服务) * [安全地暴露内网服务](#安全地暴露内网服务)
* [点对点内网穿透](#点对点内网穿透) * [点对点内网穿透](#点对点内网穿透)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网) * [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
@@ -28,8 +29,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [加密与压缩](#加密与压缩) * [加密与压缩](#加密与压缩)
* [客户端热加载配置文件](#客户端热加载配置文件) * [客户端热加载配置文件](#客户端热加载配置文件)
* [客户端查看代理状态](#客户端查看代理状态) * [客户端查看代理状态](#客户端查看代理状态)
* [特权模式](#特权模式) * [端口白名单](#端口白名单)
* [端口白名单](#端口白名单)
* [TCP 多路复用](#tcp-多路复用) * [TCP 多路复用](#tcp-多路复用)
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议) * [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
* [连接池](#连接池) * [连接池](#连接池)
@@ -39,6 +39,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [自定义二级域名](#自定义二级域名) * [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由) * [URL 路由](#url-路由)
* [通过代理连接 frps](#通过代理连接-frps) * [通过代理连接 frps](#通过代理连接-frps)
* [范围端口映射](#范围端口映射)
* [插件](#插件) * [插件](#插件)
* [开发计划](#开发计划) * [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献) * [为 frp 做贡献](#为-frp-做贡献)
@@ -192,11 +193,11 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
### 转发 Unix域套接字 ### 转发 Unix域套接字
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。 通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
frps 的部署步骤同上。 frps 的部署步骤同上。
1. 启动 frpc启用 unix_domain_socket 插件,配置如下: 1. 启动 frpc启用 `unix_domain_socket` 插件,配置如下:
```ini ```ini
# frpc.ini # frpc.ini
@@ -215,6 +216,34 @@ frps 的部署步骤同上。
`curl http://x.x.x.x:6000/version` `curl http://x.x.x.x:6000/version`
### 对外提供简单的文件访问服务
通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。
frps 的部署步骤同上。
1. 启动 frpc启用 `static_file` 插件,配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
# 要对外暴露的文件目录
plugin_local_path = /tmp/file
# 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
```
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
### 安全地暴露内网服务 ### 安全地暴露内网服务
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。 对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
@@ -371,7 +400,7 @@ dashboard_pwd = admin
### 身份验证 ### 身份验证
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。 从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。 需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
@@ -420,21 +449,17 @@ admin_port = 7400
frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。 frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。
### 特权模式 ### 端口白名单
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。 为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定:
#### 端口白名单
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
```ini ```ini
# frps.ini # frps.ini
[common] [common]
privilege_allow_ports = 2000-3000,3001,3003,4000-50000 allow_ports = 2000-3000,3001,3003,4000-50000
``` ```
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。 `allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### TCP 多路复用 ### TCP 多路复用
@@ -609,11 +634,30 @@ server_port = 7000
http_proxy = http://user:pwd@192.168.1.128:8080 http_proxy = http://user:pwd@192.168.1.128:8080
``` ```
### 范围端口映射
在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。
这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy每一个 proxy 以数字为后缀命名。
例如要映射本地 6000-6005, 6007 这6个端口主要配置如下
```ini
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
```
实际连接成功后会创建 6 个 proxy命名为 `test_tcp_0, test_tcp_1 ... test_tcp_5`。
### 插件 ### 插件
默认情况下frpc 只会转发请求到本地 tcp 或 udp 端口。 默认情况下frpc 只会转发请求到本地 tcp 或 udp 端口。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。 插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。 通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
@@ -638,8 +682,6 @@ plugin_http_passwd = abc
* frps 记录 http 请求日志。 * frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。 * frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。 * frpc 支持负载均衡到后端不同服务。
* frpc 支持直接作为 webserver 访问指定静态页面。
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
* 集成对 k8s 等平台的支持。 * 集成对 k8s 等平台的支持。
## 为 frp 做贡献 ## 为 frp 做贡献

View File

@@ -20,7 +20,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
@@ -35,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// url router // url router
router := httprouter.New() router := httprouter.New()
user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
// api, see dashboard_api.go // api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd)) router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))

View File

@@ -17,6 +17,7 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@@ -24,6 +25,7 @@ import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
) )
@@ -51,15 +53,16 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
log.Info("Http request: [/api/reload]") log.Info("Http request: [/api/reload]")
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile) b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
if err != nil { if err != nil {
res.Code = 1 res.Code = 1
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err) log.Error("reload frpc config file error: %v", err)
return return
} }
content := string(b)
newCommonCfg, err := config.LoadClientCommonConf(conf) newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
if err != nil { if err != nil {
res.Code = 2 res.Code = 2
res.Msg = err.Error() res.Msg = err.Error()
@@ -67,7 +70,15 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start) conf, err := ini.LoadFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 3 res.Code = 3
res.Msg = err.Error() res.Msg = err.Error()
@@ -125,18 +136,18 @@ func NewProxyStatusResp(status *ProxyStatus) ProxyStatusResp {
} }
psr.Plugin = cfg.Plugin psr.Plugin = cfg.Plugin
if status.Err != "" { if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort) psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
} else { } else {
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
if cfg.LocalPort != 0 { if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
} }
if status.Err != "" { if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, cfg.RemotePort) psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
} else { } else {
psr.RemoteAddr = config.ClientCommonCfg.ServerAddr + status.RemoteAddr psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
if cfg.LocalPort != 0 { if cfg.LocalPort != 0 {

View File

@@ -21,6 +21,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto" "github.com/fatedier/frp/utils/crypto"
@@ -29,7 +30,8 @@ import (
"github.com/fatedier/frp/utils/shutdown" "github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
"github.com/xtaci/smux"
fmux "github.com/hashicorp/yamux"
) )
const ( const (
@@ -49,7 +51,7 @@ type Control struct {
conn frpNet.Conn conn frpNet.Conn
// tcp stream multiplexing, if enabled // tcp stream multiplexing, if enabled
session *smux.Session session *fmux.Session
// put a message in this channel to send it over control connection to server // put a message in this channel to send it over control connection to server
sendCh chan (msg.Message) sendCh chan (msg.Message)
@@ -82,15 +84,15 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m
loginMsg := &msg.Login{ loginMsg := &msg.Login{
Arch: runtime.GOARCH, Arch: runtime.GOARCH,
Os: runtime.GOOS, Os: runtime.GOOS,
PoolCount: config.ClientCommonCfg.PoolCount, PoolCount: g.GlbClientCfg.PoolCount,
User: config.ClientCommonCfg.User, User: g.GlbClientCfg.User,
Version: version.Full(), Version: version.Full(),
} }
ctl := &Control{ ctl := &Control{
svr: svr, svr: svr,
loginMsg: loginMsg, loginMsg: loginMsg,
sendCh: make(chan msg.Message, 10), sendCh: make(chan msg.Message, 100),
readCh: make(chan msg.Message, 10), readCh: make(chan msg.Message, 100),
closedCh: make(chan int), closedCh: make(chan int),
readerShutdown: shutdown.New(), readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(), writerShutdown: shutdown.New(),
@@ -98,7 +100,7 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m
Logger: log.NewPrefixLogger(""), Logger: log.NewPrefixLogger(""),
} }
ctl.pm = NewProxyManager(ctl, ctl.sendCh, "") ctl.pm = NewProxyManager(ctl, ctl.sendCh, "")
ctl.pm.Reload(pxyCfgs, visitorCfgs) ctl.pm.Reload(pxyCfgs, visitorCfgs, false)
return ctl return ctl
} }
@@ -110,7 +112,7 @@ func (ctl *Control) Run() (err error) {
// if login_fail_exit is true, just exit this program // if login_fail_exit is true, just exit this program
// otherwise sleep a while and continues relogin to server // otherwise sleep a while and continues relogin to server
if config.ClientCommonCfg.LoginFailExit { if g.GlbClientCfg.LoginFailExit {
return return
} else { } else {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
@@ -124,7 +126,7 @@ func (ctl *Control) Run() (err error) {
// start all local visitors and send NewProxy message for all configured proxies // start all local visitors and send NewProxy message for all configured proxies
ctl.pm.Reset(ctl.sendCh, ctl.runId) ctl.pm.Reset(ctl.sendCh, ctl.runId)
ctl.pm.CheckAndStartProxy() ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew})
return nil return nil
} }
@@ -183,8 +185,8 @@ func (ctl *Control) login() (err error) {
ctl.session.Close() ctl.session.Close()
} }
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol, conn, err := frpNet.ConnectServerByHttpProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil { if err != nil {
return err return err
} }
@@ -195,8 +197,8 @@ func (ctl *Control) login() (err error) {
} }
}() }()
if config.ClientCommonCfg.TcpMux { if g.GlbClientCfg.TcpMux {
session, errRet := smux.Client(conn, nil) session, errRet := fmux.Client(conn, nil)
if errRet != nil { if errRet != nil {
return errRet return errRet
} }
@@ -210,7 +212,7 @@ func (ctl *Control) login() (err error) {
} }
now := time.Now().Unix() now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now) ctl.loginMsg.PrivilegeKey = util.GetAuthKey(g.GlbClientCfg.Token, now)
ctl.loginMsg.Timestamp = now ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.runId ctl.loginMsg.RunId = ctl.runId
@@ -234,7 +236,7 @@ func (ctl *Control) login() (err error) {
ctl.conn = conn ctl.conn = conn
// update runId got from server // update runId got from server
ctl.runId = loginRespMsg.RunId ctl.runId = loginRespMsg.RunId
config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
ctl.ClearLogPrefix() ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId) ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort) ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
@@ -242,7 +244,7 @@ func (ctl *Control) login() (err error) {
} }
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
if config.ClientCommonCfg.TcpMux { if g.GlbClientCfg.TcpMux {
stream, errRet := ctl.session.OpenStream() stream, errRet := ctl.session.OpenStream()
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -251,8 +253,8 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
} }
conn = frpNet.WrapConn(stream) conn = frpNet.WrapConn(stream)
} else { } else {
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol, conn, err = frpNet.ConnectServerByHttpProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
if err != nil { if err != nil {
ctl.Warn("start new connection to server error: %v", err) ctl.Warn("start new connection to server error: %v", err)
return return
@@ -271,7 +273,7 @@ func (ctl *Control) reader() {
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
defer close(ctl.closedCh) defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken)) encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
for { for {
if m, err := msg.ReadMsg(encReader); err != nil { if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF { if err == io.EOF {
@@ -290,7 +292,7 @@ func (ctl *Control) reader() {
// writer writes messages got from sendCh to frps // writer writes messages got from sendCh to frps
func (ctl *Control) writer() { func (ctl *Control) writer() {
defer ctl.writerShutdown.Done() defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken)) encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
if err != nil { if err != nil {
ctl.conn.Error("crypto new writer error: %v", err) ctl.conn.Error("crypto new writer error: %v", err)
ctl.conn.Close() ctl.conn.Close()
@@ -318,7 +320,7 @@ func (ctl *Control) msgHandler() {
}() }()
defer ctl.msgHandlerShutdown.Done() defer ctl.msgHandlerShutdown.Done()
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second) hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop() defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second) hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop() defer hbCheck.Stop()
@@ -332,7 +334,7 @@ func (ctl *Control) msgHandler() {
ctl.Debug("send heartbeat to server") ctl.Debug("send heartbeat to server")
ctl.sendCh <- &msg.Ping{} ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C: case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second { if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout") ctl.Warn("heartbeat timeout")
// let reader() stop // let reader() stop
ctl.conn.Close() ctl.conn.Close()
@@ -360,20 +362,20 @@ func (ctl *Control) msgHandler() {
// If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions. // If controler is notified by closedCh, reader and writer and handler will exit, then recall these functions.
func (ctl *Control) worker() { func (ctl *Control) worker() {
go ctl.msgHandler() go ctl.msgHandler()
go ctl.writer()
go ctl.reader() go ctl.reader()
go ctl.writer()
var err error var err error
maxDelayTime := 20 * time.Second maxDelayTime := 20 * time.Second
delayTime := time.Second delayTime := time.Second
checkInterval := 10 * time.Second checkInterval := 60 * time.Second
checkProxyTicker := time.NewTicker(checkInterval) checkProxyTicker := time.NewTicker(checkInterval)
for { for {
select { select {
case <-checkProxyTicker.C: case <-checkProxyTicker.C:
// every 10 seconds, check which proxy registered failed and reregister it to server // check which proxy registered failed and reregister it to server
ctl.pm.CheckAndStartProxy() ctl.pm.CheckAndStartProxy([]string{ProxyStatusStartErr, ProxyStatusClosed})
case _, ok := <-ctl.closedCh: case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel // we won't get any variable from this channel
if !ok { if !ok {
@@ -413,8 +415,8 @@ func (ctl *Control) worker() {
} }
// init related channels and variables // init related channels and variables
ctl.sendCh = make(chan msg.Message, 10) ctl.sendCh = make(chan msg.Message, 100)
ctl.readCh = make(chan msg.Message, 10) ctl.readCh = make(chan msg.Message, 100)
ctl.closedCh = make(chan int) ctl.closedCh = make(chan int)
ctl.readerShutdown = shutdown.New() ctl.readerShutdown = shutdown.New()
ctl.writerShutdown = shutdown.New() ctl.writerShutdown = shutdown.New()
@@ -427,7 +429,7 @@ func (ctl *Control) worker() {
go ctl.reader() go ctl.reader()
// start all configured proxies // start all configured proxies
ctl.pm.CheckAndStartProxy() ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew, ProxyStatusClosed})
checkProxyTicker.Stop() checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval) checkProxyTicker = time.NewTicker(checkInterval)
@@ -437,6 +439,6 @@ func (ctl *Control) worker() {
} }
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error { func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error {
err := ctl.pm.Reload(pxyCfgs, visitorCfgs) err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true)
return err return err
} }

View File

@@ -22,6 +22,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/plugin" "github.com/fatedier/frp/models/plugin"
@@ -46,7 +47,7 @@ type Proxy interface {
func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) { func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
baseProxy := BaseProxy{ baseProxy := BaseProxy{
Logger: log.NewPrefixLogger(pxyConf.GetName()), Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
@@ -115,7 +116,7 @@ func (pxy *TcpProxy) Close() {
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) { 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)) []byte(g.GlbClientCfg.Token))
} }
// HTTP // HTTP
@@ -144,7 +145,7 @@ func (pxy *HttpProxy) Close() {
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) { 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)) []byte(g.GlbClientCfg.Token))
} }
// HTTPS // HTTPS
@@ -173,7 +174,7 @@ func (pxy *HttpsProxy) Close() {
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) { 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)) []byte(g.GlbClientCfg.Token))
} }
// STCP // STCP
@@ -202,7 +203,7 @@ func (pxy *StcpProxy) Close() {
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) { 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)) []byte(g.GlbClientCfg.Token))
} }
// XTCP // XTCP
@@ -243,7 +244,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
Sid: natHoleSidMsg.Sid, Sid: natHoleSidMsg.Sid,
} }
raddr, _ := net.ResolveUDPAddr("udp", raddr, _ := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
clientConn, err := net.DialUDP("udp", nil, raddr) clientConn, err := net.DialUDP("udp", nil, raddr)
defer clientConn.Close() defer clientConn.Close()

View File

@@ -12,10 +12,11 @@ import (
) )
const ( const (
ProxyStatusNew = "new" ProxyStatusNew = "new"
ProxyStatusStartErr = "start error" ProxyStatusStartErr = "start error"
ProxyStatusRunning = "running" ProxyStatusWaitStart = "wait start"
ProxyStatusClosed = "closed" ProxyStatusRunning = "running"
ProxyStatusClosed = "closed"
) )
type ProxyManager struct { type ProxyManager struct {
@@ -61,22 +62,18 @@ type ProxyStatus struct {
func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper { func NewProxyWrapper(cfg config.ProxyConf) *ProxyWrapper {
return &ProxyWrapper{ return &ProxyWrapper{
Name: cfg.GetName(), Name: cfg.GetBaseInfo().ProxyName,
Type: cfg.GetType(), Type: cfg.GetBaseInfo().ProxyType,
Status: ProxyStatusNew, Status: ProxyStatusNew,
Cfg: cfg, Cfg: cfg,
pxy: nil, pxy: nil,
} }
} }
func (pw *ProxyWrapper) IsRunning() bool { func (pw *ProxyWrapper) GetStatusStr() string {
pw.mu.RLock() pw.mu.RLock()
defer pw.mu.RUnlock() defer pw.mu.RUnlock()
if pw.Status == ProxyStatusRunning { return pw.Status
return true
} else {
return false
}
} }
func (pw *ProxyWrapper) GetStatus() *ProxyStatus { func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
@@ -93,6 +90,12 @@ func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
return ps return ps
} }
func (pw *ProxyWrapper) WaitStart() {
pw.mu.Lock()
defer pw.mu.Unlock()
pw.Status = ProxyStatusWaitStart
}
func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error { func (pw *ProxyWrapper) Start(remoteAddr string, serverRespErr string) error {
if pw.pxy != nil { if pw.pxy != nil {
pw.pxy.Close() pw.pxy.Close()
@@ -210,7 +213,8 @@ func (pm *ProxyManager) CloseProxies() {
} }
} }
func (pm *ProxyManager) CheckAndStartProxy() { // pxyStatus: check and start proxies in which status
func (pm *ProxyManager) CheckAndStartProxy(pxyStatus []string) {
pm.mu.RLock() pm.mu.RLock()
defer pm.mu.RUnlock() defer pm.mu.RUnlock()
if pm.closed { if pm.closed {
@@ -219,35 +223,46 @@ func (pm *ProxyManager) CheckAndStartProxy() {
} }
for _, pxy := range pm.proxies { for _, pxy := range pm.proxies {
if !pxy.IsRunning() { status := pxy.GetStatusStr()
var newProxyMsg msg.NewProxy for _, s := range pxyStatus {
pxy.Cfg.UnMarshalToMsg(&newProxyMsg) if status == s {
err := pm.sendMsg(&newProxyMsg) var newProxyMsg msg.NewProxy
if err != nil { pxy.Cfg.MarshalToMsg(&newProxyMsg)
pm.Warn("[%s] proxy send NewProxy message error") err := pm.sendMsg(&newProxyMsg)
return if err != nil {
pm.Warn("[%s] proxy send NewProxy message error")
return
}
pxy.WaitStart()
break
} }
} }
} }
for _, cfg := range pm.visitorCfgs { for _, cfg := range pm.visitorCfgs {
if _, exist := pm.visitors[cfg.GetName()]; !exist { name := cfg.GetBaseInfo().ProxyName
pm.Info("try to start visitor [%s]", cfg.GetName()) if _, exist := pm.visitors[name]; !exist {
pm.Info("try to start visitor [%s]", name)
visitor := NewVisitor(pm.ctl, cfg) visitor := NewVisitor(pm.ctl, cfg)
err := visitor.Run() err := visitor.Run()
if err != nil { if err != nil {
visitor.Warn("start error: %v", err) visitor.Warn("start error: %v", err)
continue continue
} }
pm.visitors[cfg.GetName()] = visitor pm.visitors[name] = visitor
visitor.Info("start visitor success") visitor.Info("start visitor success")
} }
} }
} }
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error { func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf, startNow bool) error {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer func() {
pm.mu.Unlock()
if startNow {
go pm.CheckAndStartProxy([]string{ProxyStatusNew})
}
}()
if pm.closed { if pm.closed {
err := fmt.Errorf("Reload error: ProxyManager is closed now") err := fmt.Errorf("Reload error: ProxyManager is closed now")
pm.Warn(err.Error()) pm.Warn(err.Error())

View File

@@ -15,6 +15,7 @@
package client package client
import ( import (
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
) )
@@ -41,12 +42,12 @@ func (svr *Service) Run() error {
return err return err
} }
if config.ClientCommonCfg.AdminPort != 0 { if g.GlbClientCfg.AdminPort != 0 {
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort) err = svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
if err != nil { if err != nil {
log.Warn("run admin server error: %v", err) log.Warn("run admin server error: %v", err)
} }
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort) log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
} }
<-svr.closedCh <-svr.closedCh

View File

@@ -26,6 +26,7 @@ import (
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
frpIo "github.com/fatedier/frp/utils/io" frpIo "github.com/fatedier/frp/utils/io"
@@ -45,7 +46,7 @@ type Visitor interface {
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) { func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
baseVisitor := BaseVisitor{ baseVisitor := BaseVisitor{
ctl: ctl, ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()), Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.StcpProxyConf: case *config.StcpProxyConf:
@@ -193,13 +194,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
defer userConn.Close() defer userConn.Close()
sv.Debug("get a new xtcp user connection") sv.Debug("get a new xtcp user connection")
if config.ClientCommonCfg.ServerUdpPort == 0 { if g.GlbClientCfg.ServerUdpPort == 0 {
sv.Error("xtcp is not supported by server") sv.Error("xtcp is not supported by server")
return return
} }
raddr, err := net.ResolveUDPAddr("udp", raddr, err := net.ResolveUDPAddr("udp",
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort)) fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
visitorConn, err := net.DialUDP("udp", nil, raddr) visitorConn, err := net.DialUDP("udp", nil, raddr)
defer visitorConn.Close() defer visitorConn.Close()

View File

@@ -15,291 +15,9 @@
package main package main
import ( import (
"encoding/base64" "github.com/fatedier/frp/cmd/frpc/sub"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
docopt "github.com/docopt/docopt-go"
"github.com/rodaine/table"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
) )
var (
configFile string = "./frpc.ini"
)
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 reload [-c config_file]
frpc status [-c config_file]
frpc -h | --help
frpc -v | --version
Options:
-c config_file set config file
-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
-h --help show this screen
-v --version show version
`
func main() { func main() {
var err error sub.Execute()
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)
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg.ConfigFile = confFile
// check if reload command
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)
}
}
}
// 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 {
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ClientCommonCfg.LogWay = "console"
} else {
config.ClientCommonCfg.LogWay = "file"
config.ClientCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
addr := strings.Split(args["--server-addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = int(serverPort)
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
svr := client.NewService(pxyCfgs, visitorCfgs)
// Capture the exit signal if we use kcp.
if config.ClientCommonCfg.Protocol == "kcp" {
go HandleSignal(svr)
}
err = svr.Run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func HandleSignal(svr *client.Service) {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.Close()
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
} }

96
cmd/frpc/sub/http.go Normal file
View File

@@ -0,0 +1,96 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "admin", "http auth user")
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "admin", "http auth password")
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(httpCmd)
}
var httpCmd = &cobra.Command{
Use: "http",
Short: "Run frpc with a single http proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.HttpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.Locations = strings.Split(locations, ",")
cfg.HttpUser = httpUser
cfg.HttpPwd = httpPwd
cfg.HostHeaderRewrite = hostHeaderRewrite
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

88
cmd/frpc/sub/https.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(httpsCmd)
}
var httpsCmd = &cobra.Command{
Use: "https",
Short: "Run frpc with a single https proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.HttpsProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpsProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

92
cmd/frpc/sub/reload.go Normal file
View File

@@ -0,0 +1,92 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
)
func init() {
rootCmd.AddCommand(reloadCmd)
}
var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = reload()
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
fmt.Printf("reload success\n")
return nil
},
}
func reload() error {
if g.GlbClientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
}
req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
g.GlbClientCfg.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
}

215
cmd/frpc/sub/root.go Normal file
View File

@@ -0,0 +1,215 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
showVersion bool
serverAddr string
user string
protocol string
token string
logLevel string
logFile string
logMaxDays int
proxyName string
localIp string
localPort int
remotePort int
useEncryption bool
useCompression bool
customDomains string
subDomain string
httpUser string
httpPwd string
locations string
hostHeaderRewrite string
role string
sk string
serverName string
bindAddr string
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
}
var rootCmd = &cobra.Command{
Use: "frpc",
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}
// Do not show command usage here.
err := runClient(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func handleSignal(svr *client.Service) {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.Close()
time.Sleep(250 * time.Millisecond)
os.Exit(0)
}
func parseClientCommonCfg(fileType int, filePath string) (err error) {
if fileType == CfgFileTypeIni {
err = parseClientCommonCfgFromIni(filePath)
} else if fileType == CfgFileTypeCmd {
err = parseClientCommonCfgFromCmd()
}
if err != nil {
return
}
g.GlbClientCfg.CfgFile = cfgFile
err = g.GlbClientCfg.ClientCommonConf.Check()
if err != nil {
return
}
return
}
func parseClientCommonCfgFromIni(filePath string) (err error) {
b, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
content := string(b)
cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
if err != nil {
return err
}
g.GlbClientCfg.ClientCommonConf = *cfg
return
}
func parseClientCommonCfgFromCmd() (err error) {
strs := strings.Split(serverAddr, ":")
if len(strs) < 2 {
err = fmt.Errorf("invalid server_addr")
return
}
if strs[0] != "" {
g.GlbClientCfg.ServerAddr = strs[0]
}
g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
if err != nil {
err = fmt.Errorf("invalid server_addr")
return
}
g.GlbClientCfg.User = user
g.GlbClientCfg.Protocol = protocol
g.GlbClientCfg.Token = token
g.GlbClientCfg.LogLevel = logLevel
g.GlbClientCfg.LogFile = logFile
g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
return nil
}
func runClient(cfgFilePath string) (err error) {
err = parseClientCommonCfg(CfgFileTypeIni, cfgFilePath)
if err != nil {
return
}
conf, err := ini.LoadFile(cfgFilePath)
if err != nil {
return err
}
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start)
if err != nil {
return err
}
err = startService(pxyCfgs, visitorCfgs)
return
}
func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (err error) {
log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
if g.GlbClientCfg.DnsServer != "" {
s := g.GlbClientCfg.DnsServer
if !strings.Contains(s, ":") {
s += ":53"
}
// Change default dns server for frpc
net.DefaultResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return net.Dial("udp", s)
},
}
}
svr := client.NewService(pxyCfgs, visitorCfgs)
// Capture the exit signal if we use kcp.
if g.GlbClientCfg.Protocol == "kcp" {
go handleSignal(svr)
}
err = svr.Run()
return
}

146
cmd/frpc/sub/status.go Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/rodaine/table"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g"
)
func init() {
rootCmd.AddCommand(statusCmd)
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "Overview of all proxies status",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeIni, cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = status()
if err != nil {
fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func status() error {
if g.GlbClientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
}
req, err := http.NewRequest("GET", "http://"+
g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
g.GlbClientCfg.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
}

102
cmd/frpc/sub/stcp.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(stcpCmd)
}
var stcpCmd = &cobra.Command{
Use: "stcp",
Short: "Run frpc with a single stcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.StcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.BindAddr = bindAddr
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if cfg.Role == "server" {
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
visitorConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(nil, visitorConfs)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
},
}

85
cmd/frpc/sub/tcp.go Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(tcpCmd)
}
var tcpCmd = &cobra.Command{
Use: "tcp",
Short: "Run frpc with a single tcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.TcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TcpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

85
cmd/frpc/sub/udp.go Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(udpCmd)
}
var udpCmd = &cobra.Command{
Use: "udp",
Short: "Run frpc with a single udp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.UdpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.UdpProxy
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

102
cmd/frpc/sub/xtcp.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(xtcpCmd)
}
var xtcpCmd = &cobra.Command{
Use: "xtcp",
Short: "Run frpc with a single xtcp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg := &config.XtcpProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XtcpProxy
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.LocalIp = localIp
cfg.LocalPort = localPort
cfg.BindAddr = bindAddr
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if cfg.Role == "server" {
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(proxyConfs, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
visitorConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(nil, visitorConfs)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
return nil
},
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com // Copyright 2018 fatedier, fatedier@gmail.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -14,105 +14,6 @@
package main package main
import (
"fmt"
"os"
"strconv"
"strings"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
-h --help show this screen
-v --version show version
`
func main() { func main() {
var err error Execute()
confFile := "./frps.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)
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ServerCommonCfg.LogWay = "console"
} else {
config.ServerCommonCfg.LogWay = "file"
config.ServerCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ServerCommonCfg.BindAddr = addr[0]
config.ServerCommonCfg.BindPort = int(bindPort)
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frps success")
if config.ServerCommonCfg.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
server.ServerService = svr
svr.Run()
} }

194
cmd/frps/root.go Normal file
View File

@@ -0,0 +1,194 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
showVersion bool
bindAddr string
bindPort int
bindUdpPort int
kcpBindPort int
proxyBindAddr string
vhostHttpPort int
vhostHttpsPort int
dashboardAddr string
dashboardPort int
dashboardUser string
dashboardPwd string
assetsDir string
logFile string
logWay string
logLevel string
logMaxDays int64
token string
authTimeout int64
subDomainHost string
tcpMux bool
allowPorts string
maxPoolCount int64
maxPortsPerClient int64
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
rootCmd.PersistentFlags().StringVarP(&logWay, "log_way", "", "console", "log way")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
rootCmd.PersistentFlags().StringVarP(&token, "token", "", "", "auth token")
rootCmd.PersistentFlags().Int64VarP(&authTimeout, "auth_timeout", "", 900, "auth timeout")
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
}
var rootCmd = &cobra.Command{
Use: "frps",
Short: "frps is the server of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}
var err error
if cfgFile != "" {
err = parseServerCommonCfg(CfgFileTypeIni, cfgFile)
} else {
err = parseServerCommonCfg(CfgFileTypeCmd, "")
}
if err != nil {
return err
}
err = runServer()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func parseServerCommonCfg(fileType int, filePath string) (err error) {
if fileType == CfgFileTypeIni {
err = parseServerCommonCfgFromIni(filePath)
} else if fileType == CfgFileTypeCmd {
err = parseServerCommonCfgFromCmd()
}
if err != nil {
return
}
g.GlbServerCfg.CfgFile = filePath
err = g.GlbServerCfg.ServerCommonConf.Check()
if err != nil {
return
}
config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
return
}
func parseServerCommonCfgFromIni(filePath string) (err error) {
b, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
content := string(b)
cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
if err != nil {
return err
}
g.GlbServerCfg.ServerCommonConf = *cfg
return
}
func parseServerCommonCfgFromCmd() (err error) {
g.GlbServerCfg.BindAddr = bindAddr
g.GlbServerCfg.BindPort = bindPort
g.GlbServerCfg.BindUdpPort = bindUdpPort
g.GlbServerCfg.KcpBindPort = kcpBindPort
g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
g.GlbServerCfg.VhostHttpPort = vhostHttpPort
g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
g.GlbServerCfg.DashboardAddr = dashboardAddr
g.GlbServerCfg.DashboardPort = dashboardPort
g.GlbServerCfg.DashboardUser = dashboardUser
g.GlbServerCfg.DashboardPwd = dashboardPwd
g.GlbServerCfg.LogFile = logFile
g.GlbServerCfg.LogWay = logWay
g.GlbServerCfg.LogLevel = logLevel
g.GlbServerCfg.LogMaxDays = logMaxDays
g.GlbServerCfg.Token = token
g.GlbServerCfg.AuthTimeout = authTimeout
g.GlbServerCfg.SubDomainHost = subDomainHost
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
return
}
func runServer() (err error) {
log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
g.GlbServerCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
return err
}
log.Info("Start frps success")
server.ServerService = svr
svr.Run()
return
}

View File

@@ -7,7 +7,7 @@ server_port = 7000
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables # if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
# it only works when protocol is tcp # it only works when protocol is tcp
# http_proxy = http://user:pwd@192.168.1.128:8080 # http_proxy = http://user:passwd@192.168.1.128:8080
# console or real logFile path like ./frpc.log # console or real logFile path like ./frpc.log
log_file = ./frpc.log log_file = ./frpc.log
@@ -18,13 +18,13 @@ log_level = info
log_max_days = 3 log_max_days = 3
# for authentication # for authentication
privilege_token = 12345678 token = 12345678
# set admin address for control frpc's action by http api such as reload # set admin address for control frpc's action by http api such as reload
admin_addr = 127.0.0.1 admin_addr = 127.0.0.1
admin_port = 7400 admin_port = 7400
admin_user = admin admin_user = admin
admin_pwd = admin admin_passwd = admin
# connections will be established in advance, default value is zero # connections will be established in advance, default value is zero
pool_count = 5 pool_count = 5
@@ -43,6 +43,9 @@ login_fail_exit = true
# now it supports tcp and kcp, default is tcp # now it supports tcp and kcp, default is tcp
protocol = tcp protocol = tcp
# specify a dns server, so frpc will use this instead of default one
dns_server = 8.8.8.8
# proxy names you want to start divided by ',' # proxy names you want to start divided by ','
# default is empty, means all proxies # default is empty, means all proxies
# start = ssh,dns # start = ssh,dns
@@ -73,6 +76,16 @@ local_port = 22
# if remote_port is 0, frps will assgin a random port for you # if remote_port is 0, frps will assgin a random port for you
remote_port = 0 remote_port = 0
# if you want to expose multiple ports, add 'range:' prefix to the section name
# frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on.
[range:tcp_port]
type = tcp
local_ip = 127.0.0.1
local_port = 6010-6020,6022,6024-6028
remote_port = 6010-6020,6022,6024-6028
use_encryption = false
use_compression = false
[dns] [dns]
type = udp type = udp
local_ip = 114.114.114.114 local_ip = 114.114.114.114
@@ -81,6 +94,14 @@ remote_port = 6002
use_encryption = false use_encryption = false
use_compression = false use_compression = false
[range:udp_port]
type = udp
local_ip = 127.0.0.1
local_port = 6010-6020
remote_port = 6010-6020
use_encryption = false
use_compression = false
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02 # Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
[web01] [web01]
type = http type = http
@@ -124,6 +145,22 @@ plugin = http_proxy
plugin_http_user = abc plugin_http_user = abc
plugin_http_passwd = abc plugin_http_passwd = abc
[plugin_socks5]
type = tcp
remote_port = 6005
plugin = socks5
plugin_user = abc
plugin_passwd = abc
[plugin_static_file]
type = tcp
remote_port = 6006
plugin = static_file
plugin_local_path = /var/www/blog
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
[secret_tcp] [secret_tcp]
# If the type is secret tcp, remote_port is useless # If the type is secret tcp, remote_port is useless
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor # Who want to connect local port should deploy another frpc with stcp proxy and role is visitor

View File

@@ -25,9 +25,9 @@ vhost_https_port = 443
dashboard_addr = 0.0.0.0 dashboard_addr = 0.0.0.0
dashboard_port = 7500 dashboard_port = 7500
# dashboard user and pwd for basic auth protect, if not set, both default value is admin # dashboard user and passwd for basic auth protect, if not set, both default value is admin
dashboard_user = admin dashboard_user = admin
dashboard_pwd = admin dashboard_passwd = admin
# dashboard assets directory(only for debug mode) # dashboard assets directory(only for debug mode)
# assets_dir = ./static # assets_dir = ./static
@@ -39,19 +39,22 @@ log_level = info
log_max_days = 3 log_max_days = 3
# privilege mode is the only supported mode since v0.10.0 # auth token
privilege_token = 12345678 token = 12345678
# heartbeat configure, it's not recommended to modify the default value # heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_timeout is 90 # the default value of heartbeat_timeout is 90
# heartbeat_timeout = 90 # heartbeat_timeout = 90
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit # only allow frpc to bind ports you list, if you set nothing, there won't be any limit
privilege_allow_ports = 2000-3000,3001,3003,4000-50000 allow_ports = 2000-3000,3001,3003,4000-50000
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value # pool_count in each proxy will change to max_pool_count if they exceed the maximum value
max_pool_count = 5 max_pool_count = 5
# max ports can be used for each client, default value is 0 means no limit
max_ports_per_client = 0
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps # authentication_timeout means the timeout interval (seconds) when the frpc connects frps
# if authentication_timeout is zero, the time is not verified, default is 900s # if authentication_timeout is zero, the time is not verified, default is 900s
authentication_timeout = 900 authentication_timeout = 900

32
g/g.go Normal file
View File

@@ -0,0 +1,32 @@
package g
import (
"github.com/fatedier/frp/models/config"
)
var (
GlbClientCfg *ClientCfg
GlbServerCfg *ServerCfg
)
func init() {
GlbClientCfg = &ClientCfg{
ClientCommonConf: *config.GetDefaultClientConf(),
}
GlbServerCfg = &ServerCfg{
ServerCommonConf: *config.GetDefaultServerConf(),
}
}
type ClientCfg struct {
config.ClientCommonConf
CfgFile string
ServerUdpPort int // this is configured by login response from frps
}
type ServerCfg struct {
config.ServerCommonConf
CfgFile string
}

20
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 4095d78a15bf0e7ffdd63331ce75d7199d663cc8710dcd08b9dcd09ba3183eac hash: e2a62cbc49d9da8ff95682f5c0b7731a7047afdd139acddb691c51ea98f726e1
updated: 2018-01-23T14:48:38.764359+08:00 updated: 2018-04-25T02:41:38.15698+08:00
imports: imports:
- name: github.com/armon/go-socks5 - name: github.com/armon/go-socks5
version: e75332964ef517daa070d7c38a9466a0d687e0a5 version: e75332964ef517daa070d7c38a9466a0d687e0a5
@@ -7,8 +7,6 @@ imports:
version: 346938d642f2ec3594ed81d874461961cd0faa76 version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages: subpackages:
- spew - spew
- name: github.com/docopt/docopt-go
version: 784ddc588536785e7299f7272f39101f7faccc3f
- name: github.com/fatedier/beego - name: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8 version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages: subpackages:
@@ -18,7 +16,11 @@ imports:
- name: github.com/golang/snappy - name: github.com/golang/snappy
version: 5979233c5d6225d4a8e438cdd0b411888449ddab version: 5979233c5d6225d4a8e438cdd0b411888449ddab
- name: github.com/gorilla/websocket - name: github.com/gorilla/websocket
version: 292fd08b2560ad524ee37396253d71570339a821 version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/hashicorp/yamux
version: 2658be15c5f05e76244154714161f17e3e77de2e
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/julienschmidt/httprouter - name: github.com/julienschmidt/httprouter
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
- name: github.com/klauspost/cpuid - name: github.com/klauspost/cpuid
@@ -37,6 +39,10 @@ imports:
- fs - fs
- name: github.com/rodaine/table - name: github.com/rodaine/table
version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd
- name: github.com/spf13/cobra
version: a1f051bc3eba734da4772d60e2d677f47cf93ef4
- name: github.com/spf13/pflag
version: 583c0c0531f06d5278b7d917446061adc344b5cd
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 2402e8e7a02fc811447d11f881aa9746cdc57983 version: 2402e8e7a02fc811447d11f881aa9746cdc57983
subpackages: subpackages:
@@ -53,10 +59,6 @@ imports:
- sm4 - sm4
- name: github.com/vaughan0/go-ini - name: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1 version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- name: github.com/xtaci/kcp-go
version: df437e2b8ec365a336200f9d9da53441cf72ed47
- name: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages: subpackages:

View File

@@ -6,8 +6,6 @@ import:
version: v1.1.0 version: v1.1.0
subpackages: subpackages:
- spew - spew
- package: github.com/docopt/docopt-go
version: 0.6.2
- package: github.com/fatedier/beego - package: github.com/fatedier/beego
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8 version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
subpackages: subpackages:
@@ -48,10 +46,6 @@ import:
- sm4 - sm4
- package: github.com/vaughan0/go-ini - package: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1 version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- package: github.com/xtaci/kcp-go
version: v3.17
- package: github.com/xtaci/smux
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
- package: golang.org/x/crypto - package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages: subpackages:
@@ -74,3 +68,7 @@ import:
- package: github.com/rodaine/table - package: github.com/rodaine/table
version: v1.0.0 version: v1.0.0
- package: github.com/gorilla/websocket - package: github.com/gorilla/websocket
version: v1.2.0
- package: github.com/hashicorp/yamux
- package: github.com/spf13/cobra
version: v0.0.2

View File

@@ -23,46 +23,41 @@ import (
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
) )
var ClientCommonCfg *ClientCommonConf
// client common config // client common config
type ClientCommonConf struct { type ClientCommonConf struct {
ConfigFile string ServerAddr string `json:"server_addr"`
ServerAddr string ServerPort int `json:"server_port"`
ServerPort int HttpProxy string `json:"http_proxy"`
ServerUdpPort int // this is specified by login response message from frps LogFile string `json:"log_file"`
HttpProxy string LogWay string `json:"log_way"`
LogFile string LogLevel string `json:"log_level"`
LogWay string LogMaxDays int64 `json:"log_max_days"`
LogLevel string Token string `json:"token"`
LogMaxDays int64 AdminAddr string `json:"admin_addr"`
PrivilegeToken string AdminPort int `json:"admin_port"`
AdminAddr string AdminUser string `json:"admin_user"`
AdminPort int AdminPwd string `json:"admin_pwd"`
AdminUser string PoolCount int `json:"pool_count"`
AdminPwd string TcpMux bool `json:"tcp_mux"`
PoolCount int User string `json:"user"`
TcpMux bool DnsServer string `json:"dns_server"`
User string LoginFailExit bool `json:"login_fail_exit"`
LoginFailExit bool Start map[string]struct{} `json:"start"`
Start map[string]struct{} Protocol string `json:"protocol"`
Protocol string HeartBeatInterval int64 `json:"heartbeat_interval"`
HeartBeatInterval int64 HeartBeatTimeout int64 `json:"heartbeat_timeout"`
HeartBeatTimeout int64
} }
func GetDeaultClientCommonConf() *ClientCommonConf { func GetDefaultClientConf() *ClientCommonConf {
return &ClientCommonConf{ return &ClientCommonConf{
ConfigFile: "./frpc.ini",
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
ServerUdpPort: 0, HttpProxy: os.Getenv("http_proxy"),
HttpProxy: "",
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
PrivilegeToken: "", Token: "",
AdminAddr: "127.0.0.1", AdminAddr: "127.0.0.1",
AdminPort: 0, AdminPort: 0,
AdminUser: "", AdminUser: "",
@@ -70,6 +65,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
PoolCount: 1, PoolCount: 1,
TcpMux: true, TcpMux: true,
User: "", User: "",
DnsServer: "",
LoginFailExit: true, LoginFailExit: true,
Start: make(map[string]struct{}), Start: make(map[string]struct{}),
Protocol: "tcp", Protocol: "tcp",
@@ -78,21 +74,28 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
} }
} }
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) { func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
cfg = defaultCfg
if cfg == nil {
cfg = GetDefaultClientConf()
}
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err)
return nil, err
}
var ( var (
tmpStr string tmpStr string
ok bool ok bool
v int64 v int64
) )
cfg = GetDeaultClientCommonConf() if tmpStr, ok = conf.Get("common", "server_addr"); ok {
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
cfg.ServerAddr = tmpStr cfg.ServerAddr = tmpStr
} }
tmpStr, ok = conf.Get("common", "server_port") if tmpStr, ok = conf.Get("common", "server_port"); ok {
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64) v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil { if err != nil {
err = fmt.Errorf("Parse conf error: invalid server_port") err = fmt.Errorf("Parse conf error: invalid server_port")
@@ -101,16 +104,11 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
cfg.ServerPort = int(v) cfg.ServerPort = int(v)
} }
tmpStr, ok = conf.Get("common", "http_proxy") if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
if ok {
cfg.HttpProxy = tmpStr cfg.HttpProxy = tmpStr
} else {
// get http_proxy from env
cfg.HttpProxy = os.Getenv("http_proxy")
} }
tmpStr, ok = conf.Get("common", "log_file") if tmpStr, ok = conf.Get("common", "log_file"); ok {
if ok {
cfg.LogFile = tmpStr cfg.LogFile = tmpStr
if cfg.LogFile == "console" { if cfg.LogFile == "console" {
cfg.LogWay = "console" cfg.LogWay = "console"
@@ -119,30 +117,25 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "log_level") if tmpStr, ok = conf.Get("common", "log_level"); ok {
if ok {
cfg.LogLevel = tmpStr cfg.LogLevel = tmpStr
} }
tmpStr, ok = conf.Get("common", "log_max_days") if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.LogMaxDays = v cfg.LogMaxDays = v
} }
} }
tmpStr, ok = conf.Get("common", "privilege_token") if tmpStr, ok = conf.Get("common", "token"); ok {
if ok { cfg.Token = tmpStr
cfg.PrivilegeToken = tmpStr
} }
tmpStr, ok = conf.Get("common", "admin_addr") if tmpStr, ok = conf.Get("common", "admin_addr"); ok {
if ok {
cfg.AdminAddr = tmpStr cfg.AdminAddr = tmpStr
} }
tmpStr, ok = conf.Get("common", "admin_port") if tmpStr, ok = conf.Get("common", "admin_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.AdminPort = int(v) cfg.AdminPort = int(v)
} else { } else {
@@ -151,55 +144,48 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "admin_user") if tmpStr, ok = conf.Get("common", "admin_user"); ok {
if ok {
cfg.AdminUser = tmpStr cfg.AdminUser = tmpStr
} }
tmpStr, ok = conf.Get("common", "admin_pwd") if tmpStr, ok = conf.Get("common", "admin_pwd"); ok {
if ok {
cfg.AdminPwd = tmpStr cfg.AdminPwd = tmpStr
} }
tmpStr, ok = conf.Get("common", "pool_count") if tmpStr, ok = conf.Get("common", "pool_count"); ok {
if ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
cfg.PoolCount = 1
} else {
cfg.PoolCount = int(v) cfg.PoolCount = int(v)
} }
} }
tmpStr, ok = conf.Get("common", "tcp_mux") if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
if ok && tmpStr == "false" {
cfg.TcpMux = false cfg.TcpMux = false
} else { } else {
cfg.TcpMux = true cfg.TcpMux = true
} }
tmpStr, ok = conf.Get("common", "user") if tmpStr, ok = conf.Get("common", "user"); ok {
if ok {
cfg.User = tmpStr cfg.User = tmpStr
} }
tmpStr, ok = conf.Get("common", "start") if tmpStr, ok = conf.Get("common", "dns_server"); ok {
if ok { cfg.DnsServer = tmpStr
}
if tmpStr, ok = conf.Get("common", "start"); ok {
proxyNames := strings.Split(tmpStr, ",") proxyNames := strings.Split(tmpStr, ",")
for _, name := range proxyNames { for _, name := range proxyNames {
cfg.Start[strings.TrimSpace(name)] = struct{}{} cfg.Start[strings.TrimSpace(name)] = struct{}{}
} }
} }
tmpStr, ok = conf.Get("common", "login_fail_exit") if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" {
if ok && tmpStr == "false" {
cfg.LoginFailExit = false cfg.LoginFailExit = false
} else { } else {
cfg.LoginFailExit = true cfg.LoginFailExit = true
} }
tmpStr, ok = conf.Get("common", "protocol") if tmpStr, ok = conf.Get("common", "protocol"); ok {
if ok {
// Now it only support tcp and kcp. // Now it only support tcp and kcp.
if tmpStr != "kcp" { if tmpStr != "kcp" {
tmpStr = "tcp" tmpStr = "tcp"
@@ -207,10 +193,8 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
cfg.Protocol = tmpStr cfg.Protocol = tmpStr
} }
tmpStr, ok = conf.Get("common", "heartbeat_timeout") if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
return return
} else { } else {
@@ -218,17 +202,18 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "heartbeat_interval") if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
if ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return return
} else { } else {
cfg.HeartBeatInterval = v cfg.HeartBeatInterval = v
} }
} }
return
}
func (cfg *ClientCommonConf) Check() (err error) {
if cfg.HeartBeatInterval <= 0 { if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return return

View File

@@ -22,11 +22,14 @@ import (
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
) )
var proxyConfTypeMap map[string]reflect.Type var (
proxyConfTypeMap map[string]reflect.Type
)
func init() { func init() {
proxyConfTypeMap = make(map[string]reflect.Type) proxyConfTypeMap = make(map[string]reflect.Type)
@@ -50,17 +53,16 @@ func NewConfByType(proxyType string) ProxyConf {
} }
type ProxyConf interface { type ProxyConf interface {
GetName() string
GetType() string
GetBaseInfo() *BaseProxyConf GetBaseInfo() *BaseProxyConf
LoadFromMsg(pMsg *msg.NewProxy) UnmarshalFromMsg(pMsg *msg.NewProxy)
LoadFromFile(name string, conf ini.Section) error UnmarshalFromIni(prefix string, name string, conf ini.Section) error
UnMarshalToMsg(pMsg *msg.NewProxy) MarshalToMsg(pMsg *msg.NewProxy)
Check() error CheckForCli() error
CheckForSvr() error
Compare(conf ProxyConf) bool Compare(conf ProxyConf) bool
} }
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) { func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" { if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy pMsg.ProxyType = consts.TcpProxy
} }
@@ -70,12 +72,12 @@ func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType) err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
return return
} }
cfg.LoadFromMsg(pMsg) cfg.UnmarshalFromMsg(pMsg)
err = cfg.Check() err = cfg.CheckForSvr()
return return
} }
func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) { func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) {
proxyType := section["type"] proxyType := section["type"]
if proxyType == "" { if proxyType == "" {
proxyType = consts.TcpProxy proxyType = consts.TcpProxy
@@ -86,7 +88,10 @@ func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType) err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
return return
} }
err = cfg.LoadFromFile(name, section) if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
err = cfg.CheckForCli()
return return
} }
@@ -99,14 +104,6 @@ type BaseProxyConf struct {
UseCompression bool `json:"use_compression"` UseCompression bool `json:"use_compression"`
} }
func (cfg *BaseProxyConf) GetName() string {
return cfg.ProxyName
}
func (cfg *BaseProxyConf) GetType() string {
return cfg.ProxyType
}
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
return cfg return cfg
} }
@@ -121,23 +118,19 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
return true return true
} }
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName cfg.ProxyName = pMsg.ProxyName
cfg.ProxyType = pMsg.ProxyType cfg.ProxyType = pMsg.ProxyType
cfg.UseEncryption = pMsg.UseEncryption cfg.UseEncryption = pMsg.UseEncryption
cfg.UseCompression = pMsg.UseCompression cfg.UseCompression = pMsg.UseCompression
} }
func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error { func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error {
var ( var (
tmpStr string tmpStr string
ok bool ok bool
) )
if ClientCommonCfg.User != "" { cfg.ProxyName = prefix + name
cfg.ProxyName = ClientCommonCfg.User + "." + name
} else {
cfg.ProxyName = name
}
cfg.ProxyType = section["type"] cfg.ProxyType = section["type"]
tmpStr, ok = section["use_encryption"] tmpStr, ok = section["use_encryption"]
@@ -152,7 +145,7 @@ func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
return nil return nil
} }
func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.ProxyName = cfg.ProxyName pMsg.ProxyName = cfg.ProxyName
pMsg.ProxyType = cfg.ProxyType pMsg.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption pMsg.UseEncryption = cfg.UseEncryption
@@ -161,24 +154,21 @@ func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
// Bind info // Bind info
type BindInfoConf struct { type BindInfoConf struct {
BindAddr string `json:"bind_addr"` RemotePort int `json:"remote_port"`
RemotePort int `json:"remote_port"`
} }
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool { func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
if cfg.BindAddr != cmp.BindAddr || if cfg.RemotePort != cmp.RemotePort {
cfg.RemotePort != cmp.RemotePort {
return false return false
} }
return true return true
} }
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *BindInfoConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BindAddr = ServerCommonCfg.ProxyBindAddr
cfg.RemotePort = pMsg.RemotePort cfg.RemotePort = pMsg.RemotePort
} }
func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
var ( var (
tmpStr string tmpStr string
ok bool ok bool
@@ -196,14 +186,10 @@ func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err err
return nil return nil
} }
func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *BindInfoConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.RemotePort = cfg.RemotePort pMsg.RemotePort = cfg.RemotePort
} }
func (cfg *BindInfoConf) check() (err error) {
return nil
}
// Domain info // Domain info
type DomainConf struct { type DomainConf struct {
CustomDomains []string `json:"custom_domains"` CustomDomains []string `json:"custom_domains"`
@@ -218,12 +204,12 @@ func (cfg *DomainConf) compare(cmp *DomainConf) bool {
return true return true
} }
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *DomainConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain cfg.SubDomain = pMsg.SubDomain
} }
func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *DomainConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
var ( var (
tmpStr string tmpStr string
ok bool ok bool
@@ -238,42 +224,60 @@ func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error
if tmpStr, ok = section["subdomain"]; ok { if tmpStr, ok = section["subdomain"]; ok {
cfg.SubDomain = tmpStr cfg.SubDomain = tmpStr
} }
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them", name)
}
return return
} }
func (cfg *DomainConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *DomainConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.CustomDomains = cfg.CustomDomains pMsg.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain pMsg.SubDomain = cfg.SubDomain
} }
func (cfg *DomainConf) check() (err error) { func (cfg *DomainConf) check() (err error) {
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
return
}
return
}
func (cfg *DomainConf) checkForCli() (err error) {
if err = cfg.check(); err != nil {
return
}
return
}
func (cfg *DomainConf) checkForSvr() (err error) {
if err = cfg.check(); err != nil {
return
}
for _, domain := range cfg.CustomDomains { for _, domain := range cfg.CustomDomains {
if ServerCommonCfg.SubDomainHost != "" && len(strings.Split(ServerCommonCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) { if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, ServerCommonCfg.SubDomainHost) { if strings.Contains(domain, subDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, ServerCommonCfg.SubDomainHost) return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
} }
} }
} }
if cfg.SubDomain != "" { if cfg.SubDomain != "" {
if ServerCommonCfg.SubDomainHost == "" { if subDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps") return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
} }
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") { if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain") return fmt.Errorf("'.' and '*' is not supported in subdomain")
} }
} }
return nil return
} }
// Local service info // Local service info
type LocalSvrConf struct { type LocalSvrConf struct {
LocalIp string `json:"-"` LocalIp string `json:"local_ip"`
LocalPort int `json:"-"` LocalPort int `json:"local_port"`
Plugin string `json:"plugin"`
PluginParams map[string]string `json:"plugin_params"`
} }
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool { func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
@@ -281,30 +285,6 @@ func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
cfg.LocalPort != cmp.LocalPort { cfg.LocalPort != cmp.LocalPort {
return false return false
} }
return true
}
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
cfg.LocalIp = "127.0.0.1"
}
if tmpStr, ok := section["local_port"]; ok {
if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
}
return nil
}
type PluginConf struct {
Plugin string `json:"-"`
PluginParams map[string]string `json:"-"`
}
func (cfg *PluginConf) compare(cmp *PluginConf) bool {
if cfg.Plugin != cmp.Plugin || if cfg.Plugin != cmp.Plugin ||
len(cfg.PluginParams) != len(cmp.PluginParams) { len(cfg.PluginParams) != len(cmp.PluginParams) {
return false return false
@@ -318,7 +298,7 @@ func (cfg *PluginConf) compare(cmp *PluginConf) bool {
return true return true
} }
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
cfg.Plugin = section["plugin"] cfg.Plugin = section["plugin"]
cfg.PluginParams = make(map[string]string) cfg.PluginParams = make(map[string]string)
if cfg.Plugin != "" { if cfg.Plugin != "" {
@@ -329,7 +309,17 @@ func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error
} }
} }
} else { } else {
return fmt.Errorf("Parse conf error: proxy [%s] no plugin info found", name) if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
cfg.LocalIp = "127.0.0.1"
}
if tmpStr, ok := section["local_port"]; ok {
if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
}
} }
return return
} }
@@ -340,7 +330,6 @@ type TcpProxyConf struct {
BindInfoConf BindInfoConf
LocalSvrConf LocalSvrConf
PluginConf
} }
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
@@ -351,43 +340,38 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
return false return false
} }
return true return true
} }
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg) cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
} }
func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil { if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil { return
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
} }
return return
} }
func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg) cfg.BindInfoConf.MarshalToMsg(pMsg)
} }
func (cfg *TcpProxyConf) Check() (err error) { func (cfg *TcpProxyConf) CheckForCli() error { return nil }
err = cfg.BindInfoConf.check()
return func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
}
// UDP // UDP
type UdpProxyConf struct { type UdpProxyConf struct {
@@ -411,33 +395,32 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
return true return true
} }
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg) cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
} }
func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil { if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
return return
} }
func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg) cfg.BindInfoConf.MarshalToMsg(pMsg)
} }
func (cfg *UdpProxyConf) Check() (err error) { func (cfg *UdpProxyConf) CheckForCli() error { return nil }
err = cfg.BindInfoConf.check()
return func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
}
// HTTP // HTTP
type HttpProxyConf struct { type HttpProxyConf struct {
@@ -445,12 +428,11 @@ type HttpProxyConf struct {
DomainConf DomainConf
LocalSvrConf LocalSvrConf
PluginConf
Locations []string `json:"locations"` Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"` HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"-"` HttpUser string `json:"http_user"`
HttpPwd string `json:"-"` HttpPwd string `json:"http_pwd"`
} }
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
@@ -462,7 +444,6 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) || !cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.HttpUser != cmpConf.HttpUser || cfg.HttpUser != cmpConf.HttpUser ||
@@ -472,9 +453,9 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
return true return true
} }
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg) cfg.DomainConf.UnmarshalFromMsg(pMsg)
cfg.Locations = pMsg.Locations cfg.Locations = pMsg.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
@@ -482,17 +463,15 @@ func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.HttpPwd = pMsg.HttpPwd cfg.HttpPwd = pMsg.HttpPwd
} }
func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil { if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil { return
return
}
} }
var ( var (
@@ -511,9 +490,9 @@ func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err er
return return
} }
func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg) cfg.DomainConf.MarshalToMsg(pMsg)
pMsg.Locations = cfg.Locations pMsg.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
@@ -521,11 +500,20 @@ func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.HttpPwd = cfg.HttpPwd pMsg.HttpPwd = cfg.HttpPwd
} }
func (cfg *HttpProxyConf) Check() (err error) { func (cfg *HttpProxyConf) CheckForCli() (err error) {
if ServerCommonCfg.VhostHttpPort == 0 { if err = cfg.DomainConf.checkForCli(); err != nil {
return fmt.Errorf("type [http] not support when vhost_http_port is not set") return
}
return
}
func (cfg *HttpProxyConf) CheckForSvr() (err error) {
if vhostHttpPort == 0 {
err = fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
if err = cfg.DomainConf.checkForSvr(); err != nil {
return
} }
err = cfg.DomainConf.check()
return return
} }
@@ -535,7 +523,6 @@ type HttpsProxyConf struct {
DomainConf DomainConf
LocalSvrConf LocalSvrConf
PluginConf
} }
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool { func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
@@ -546,43 +533,49 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) || !cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
return false return false
} }
return true return true
} }
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg) cfg.DomainConf.UnmarshalFromMsg(pMsg)
} }
func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil { if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil { return
return
}
} }
return return
} }
func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg) cfg.DomainConf.MarshalToMsg(pMsg)
} }
func (cfg *HttpsProxyConf) Check() (err error) { func (cfg *HttpsProxyConf) CheckForCli() (err error) {
if ServerCommonCfg.VhostHttpsPort == 0 { if err = cfg.DomainConf.checkForCli(); err != nil {
return
}
return
}
func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
if vhostHttpsPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set") return fmt.Errorf("type [https] not support when vhost_https_port is not set")
} }
err = cfg.DomainConf.check() if err = cfg.DomainConf.checkForSvr(); err != nil {
return
}
return return
} }
@@ -595,7 +588,6 @@ type StcpProxyConf struct {
// used in role server // used in role server
LocalSvrConf LocalSvrConf
PluginConf
// used in role visitor // used in role visitor
ServerName string `json:"server_name"` ServerName string `json:"server_name"`
@@ -611,7 +603,6 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role || cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk || cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName || cfg.ServerName != cmpConf.ServerName ||
@@ -623,13 +614,13 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
} }
// Only for role server. // Only for role server.
func (cfg *StcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk cfg.Sk = pMsg.Sk
} }
func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
@@ -660,21 +651,33 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
} }
} else { } else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil { return
return
}
} }
} }
return return
} }
func (cfg *StcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk pMsg.Sk = cfg.Sk
} }
func (cfg *StcpProxyConf) Check() (err error) { func (cfg *StcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return
}
if cfg.Role == "visitor" {
if cfg.BindAddr == "" {
err = fmt.Errorf("bind_addr shouldn't be empty")
return
}
}
return
}
func (cfg *StcpProxyConf) CheckForSvr() (err error) {
return return
} }
@@ -687,7 +690,6 @@ type XtcpProxyConf struct {
// used in role server // used in role server
LocalSvrConf LocalSvrConf
PluginConf
// used in role visitor // used in role visitor
ServerName string `json:"server_name"` ServerName string `json:"server_name"`
@@ -703,7 +705,6 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role || cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk || cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName || cfg.ServerName != cmpConf.ServerName ||
@@ -715,13 +716,13 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
} }
// Only for role server. // Only for role server.
func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) { func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk cfg.Sk = pMsg.Sk
} }
func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) { func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
@@ -752,27 +753,71 @@ func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
} }
} else { } else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil { if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil { return
return
}
} }
} }
return return
} }
func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) { func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk pMsg.Sk = cfg.Sk
} }
func (cfg *XtcpProxyConf) Check() (err error) { func (cfg *XtcpProxyConf) CheckForCli() (err error) {
if cfg.Role != "server" && cfg.Role != "visitor" {
err = fmt.Errorf("role should be 'server' or 'visitor'")
return
}
if cfg.Role == "visitor" {
if cfg.BindAddr == "" {
err = fmt.Errorf("bind_addr shouldn't be empty")
return
}
}
return
}
func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
return
}
func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) {
localPorts, errRet := util.ParseRangeNumbers(section["local_port"])
if errRet != nil {
err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet)
return
}
remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"])
if errRet != nil {
err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet)
return
}
if len(localPorts) != len(remotePorts) {
err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name)
return
}
if len(localPorts) == 0 {
err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary", name)
return
}
sections = make(map[string]ini.Section)
for i, port := range localPorts {
subName := fmt.Sprintf("%s_%d", name, i)
subSection := copySection(section)
subSection["local_port"] = fmt.Sprintf("%d", port)
subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i])
sections[subName] = subSection
}
return return
} }
// if len(startProxy) is 0, start all // if len(startProxy) is 0, start all
// otherwise just start proxies in startProxy map // otherwise just start proxies in startProxy map
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) ( func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) (
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) { proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
if prefix != "" { if prefix != "" {
@@ -786,22 +831,49 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
proxyConfs = make(map[string]ProxyConf) proxyConfs = make(map[string]ProxyConf)
visitorConfs = make(map[string]ProxyConf) visitorConfs = make(map[string]ProxyConf)
for name, section := range conf { for name, section := range conf {
if name == "common" {
continue
}
_, shouldStart := startProxy[name] _, shouldStart := startProxy[name]
if name != "common" && (startAll || shouldStart) { if !startAll && !shouldStart {
// some proxy or visotr configure may be used this prefix continue
section["prefix"] = prefix }
cfg, err := NewProxyConfFromFile(name, section)
subSections := make(map[string]ini.Section)
if strings.HasPrefix(name, "range:") {
// range section
rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:"))
subSections, err = ParseRangeSection(rangePrefix, section)
if err != nil {
return
}
} else {
subSections[name] = section
}
for subName, subSection := range subSections {
cfg, err := NewProxyConfFromIni(prefix, subName, subSection)
if err != nil { if err != nil {
return proxyConfs, visitorConfs, err return proxyConfs, visitorConfs, err
} }
role := section["role"] role := subSection["role"]
if role == "visitor" { if role == "visitor" {
visitorConfs[prefix+name] = cfg visitorConfs[prefix+subName] = cfg
} else { } else {
proxyConfs[prefix+name] = cfg proxyConfs[prefix+subName] = cfg
} }
} }
} }
return return
} }
func copySection(section ini.Section) (out ini.Section) {
out = make(ini.Section)
for k, v := range section {
out[k] = v
}
return
}

View File

@@ -20,94 +20,113 @@ import (
"strings" "strings"
ini "github.com/vaughan0/go-ini" ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/utils/util"
) )
var ServerCommonCfg *ServerCommonConf var (
// server global configure used for generate proxy conf used in frps
proxyBindAddr string
subDomainHost string
vhostHttpPort int
vhostHttpsPort int
)
func InitServerCfg(cfg *ServerCommonConf) {
proxyBindAddr = cfg.ProxyBindAddr
subDomainHost = cfg.SubDomainHost
vhostHttpPort = cfg.VhostHttpPort
vhostHttpsPort = cfg.VhostHttpsPort
}
// common config // common config
type ServerCommonConf struct { type ServerCommonConf struct {
ConfigFile string BindAddr string `json:"bind_addr"`
BindAddr string BindPort int `json:"bind_port"`
BindPort int BindUdpPort int `json:"bind_udp_port"`
BindUdpPort int KcpBindPort int `json:"kcp_bind_port"`
KcpBindPort int ProxyBindAddr string `json:"proxy_bind_addr"`
ProxyBindAddr string
// If VhostHttpPort equals 0, don't listen a public port for http protocol. // If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol // if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int VhostHttpsPort int `json:"vhost_http_port"`
DashboardAddr string DashboardAddr string `json:"dashboard_addr"`
// if DashboardPort equals 0, dashboard is not available // if DashboardPort equals 0, dashboard is not available
DashboardPort int DashboardPort int `json:"dashboard_port"`
DashboardUser string DashboardUser string `json:"dashboard_user"`
DashboardPwd string DashboardPwd string `json:"dashboard_pwd"`
AssetsDir string AssetsDir string `json:"asserts_dir"`
LogFile string LogFile string `json:"log_file"`
LogWay string // console or file LogWay string `json:"log_way"` // console or file
LogLevel string LogLevel string `json:"log_level"`
LogMaxDays int64 LogMaxDays int64 `json:"log_max_days"`
PrivilegeMode bool Token string `json:"token"`
PrivilegeToken string AuthTimeout int64 `json:"auth_timeout"`
AuthTimeout int64 SubDomainHost string `json:"subdomain_host"`
SubDomainHost string TcpMux bool `json:"tcp_mux"`
TcpMux bool
PrivilegeAllowPorts map[int]struct{} AllowPorts map[int]struct{}
MaxPoolCount int64 MaxPoolCount int64 `json:"max_pool_count"`
HeartBeatTimeout int64 MaxPortsPerClient int64 `json:"max_ports_per_client"`
UserConnTimeout int64 HeartBeatTimeout int64 `json:"heart_beat_timeout"`
UserConnTimeout int64 `json:"user_conn_timeout"`
} }
func GetDefaultServerCommonConf() *ServerCommonConf { func GetDefaultServerConf() *ServerCommonConf {
return &ServerCommonConf{ return &ServerCommonConf{
ConfigFile: "./frps.ini", BindAddr: "0.0.0.0",
BindAddr: "0.0.0.0", BindPort: 7000,
BindPort: 7000, BindUdpPort: 0,
BindUdpPort: 0, KcpBindPort: 0,
KcpBindPort: 0, ProxyBindAddr: "0.0.0.0",
ProxyBindAddr: "0.0.0.0", VhostHttpPort: 0,
VhostHttpPort: 0, VhostHttpsPort: 0,
VhostHttpsPort: 0, DashboardAddr: "0.0.0.0",
DashboardAddr: "0.0.0.0", DashboardPort: 0,
DashboardPort: 0, DashboardUser: "admin",
DashboardUser: "admin", DashboardPwd: "admin",
DashboardPwd: "admin", AssetsDir: "",
AssetsDir: "", LogFile: "console",
LogFile: "console", LogWay: "console",
LogWay: "console", LogLevel: "info",
LogLevel: "info", LogMaxDays: 3,
LogMaxDays: 3, Token: "",
PrivilegeMode: true, AuthTimeout: 900,
PrivilegeToken: "", SubDomainHost: "",
AuthTimeout: 900, TcpMux: true,
SubDomainHost: "", AllowPorts: make(map[int]struct{}),
TcpMux: true, MaxPoolCount: 5,
PrivilegeAllowPorts: make(map[int]struct{}), MaxPortsPerClient: 0,
MaxPoolCount: 5, HeartBeatTimeout: 90,
HeartBeatTimeout: 90, UserConnTimeout: 10,
UserConnTimeout: 10,
} }
} }
// Load server common configure. func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) { cfg = defaultCfg
if cfg == nil {
cfg = GetDefaultServerConf()
}
conf, err := ini.Load(strings.NewReader(content))
if err != nil {
err = fmt.Errorf("parse ini conf file error: %v", err)
return nil, err
}
var ( var (
tmpStr string tmpStr string
ok bool ok bool
v int64 v int64
) )
cfg = GetDefaultServerCommonConf() if tmpStr, ok = conf.Get("common", "bind_addr"); ok {
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
cfg.BindAddr = tmpStr cfg.BindAddr = tmpStr
} }
tmpStr, ok = conf.Get("common", "bind_port") if tmpStr, ok = conf.Get("common", "bind_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_port") err = fmt.Errorf("Parse conf error: invalid bind_port")
return return
@@ -116,8 +135,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "bind_udp_port") if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_udp_port") err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
return return
@@ -126,8 +144,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "kcp_bind_port") if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port") err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
return return
@@ -136,15 +153,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "proxy_bind_addr") if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
if ok {
cfg.ProxyBindAddr = tmpStr cfg.ProxyBindAddr = tmpStr
} else { } else {
cfg.ProxyBindAddr = cfg.BindAddr cfg.ProxyBindAddr = cfg.BindAddr
} }
tmpStr, ok = conf.Get("common", "vhost_http_port") if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_http_port") err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
return return
@@ -155,8 +170,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
cfg.VhostHttpPort = 0 cfg.VhostHttpPort = 0
} }
tmpStr, ok = conf.Get("common", "vhost_https_port") if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_https_port") err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
return return
@@ -167,15 +181,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
cfg.VhostHttpsPort = 0 cfg.VhostHttpsPort = 0
} }
tmpStr, ok = conf.Get("common", "dashboard_addr") if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
if ok {
cfg.DashboardAddr = tmpStr cfg.DashboardAddr = tmpStr
} else { } else {
cfg.DashboardAddr = cfg.BindAddr cfg.DashboardAddr = cfg.BindAddr
} }
tmpStr, ok = conf.Get("common", "dashboard_port") if tmpStr, ok = conf.Get("common", "dashboard_port"); ok {
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid dashboard_port") err = fmt.Errorf("Parse conf error: invalid dashboard_port")
return return
@@ -186,23 +198,19 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
cfg.DashboardPort = 0 cfg.DashboardPort = 0
} }
tmpStr, ok = conf.Get("common", "dashboard_user") if tmpStr, ok = conf.Get("common", "dashboard_user"); ok {
if ok {
cfg.DashboardUser = tmpStr cfg.DashboardUser = tmpStr
} }
tmpStr, ok = conf.Get("common", "dashboard_pwd") if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok {
if ok {
cfg.DashboardPwd = tmpStr cfg.DashboardPwd = tmpStr
} }
tmpStr, ok = conf.Get("common", "assets_dir") if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
if ok {
cfg.AssetsDir = tmpStr cfg.AssetsDir = tmpStr
} }
tmpStr, ok = conf.Get("common", "log_file") if tmpStr, ok = conf.Get("common", "log_file"); ok {
if ok {
cfg.LogFile = tmpStr cfg.LogFile = tmpStr
if cfg.LogFile == "console" { if cfg.LogFile == "console" {
cfg.LogWay = "console" cfg.LogWay = "console"
@@ -211,84 +219,59 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "log_level") if tmpStr, ok = conf.Get("common", "log_level"); ok {
if ok {
cfg.LogLevel = tmpStr cfg.LogLevel = tmpStr
} }
tmpStr, ok = conf.Get("common", "log_max_days") if tmpStr, ok = conf.Get("common", "log_max_days"); ok {
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64) v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil { if err == nil {
cfg.LogMaxDays = v cfg.LogMaxDays = v
} }
} }
tmpStr, ok = conf.Get("common", "privilege_mode") cfg.Token, _ = conf.Get("common", "token")
if ok {
if tmpStr == "true" { if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
cfg.PrivilegeMode = true // e.g. 1000-2000,2001,2002,3000-4000
ports, errRet := util.ParseRangeNumbers(allowPortsStr)
if errRet != nil {
err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet)
return
}
for _, port := range ports {
cfg.AllowPorts[int(port)] = struct{}{}
} }
} }
// PrivilegeMode configure if tmpStr, ok = conf.Get("common", "max_pool_count"); ok {
if cfg.PrivilegeMode == true { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token") err = fmt.Errorf("Parse conf error: invalid max_pool_count")
return
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports") } else {
if ok { if v < 0 {
// e.g. 1000-2000,2001,2002,3000-4000 err = fmt.Errorf("Parse conf error: invalid max_pool_count")
portRanges := strings.Split(allowPortsStr, ",") return
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
}
} }
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
cfg.MaxPoolCount = v cfg.MaxPoolCount = v
} }
} }
tmpStr, ok = conf.Get("common", "authentication_timeout") if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
if ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
} else {
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
}
cfg.MaxPortsPerClient = v
}
}
if tmpStr, ok = conf.Get("common", "authentication_timeout"); ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64) v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil { if errRet != nil {
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect") err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
@@ -298,20 +281,17 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
} }
tmpStr, ok = conf.Get("common", "subdomain_host") if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
if ok {
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr)) cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
} }
tmpStr, ok = conf.Get("common", "tcp_mux") if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
if ok && tmpStr == "false" {
cfg.TcpMux = false cfg.TcpMux = false
} else { } else {
cfg.TcpMux = true cfg.TcpMux = true
} }
tmpStr, ok = conf.Get("common", "heartbeat_timeout") if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64) v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil { if errRet != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect") err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
@@ -322,3 +302,7 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
} }
return return
} }
func (cfg *ServerCommonConf) Check() (err error) {
return
}

View File

@@ -17,14 +17,11 @@ package plugin
import ( import (
"bufio" "bufio"
"encoding/base64" "encoding/base64"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"sync"
"github.com/fatedier/frp/utils/errors"
frpIo "github.com/fatedier/frp/utils/io" frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
) )
@@ -35,47 +32,6 @@ func init() {
Register(PluginHttpProxy, NewHttpProxyPlugin) Register(PluginHttpProxy, NewHttpProxyPlugin)
} }
type Listener struct {
conns chan net.Conn
closed bool
mu sync.Mutex
}
func NewProxyListener() *Listener {
return &Listener{
conns: make(chan net.Conn, 64),
}
}
func (l *Listener) Accept() (net.Conn, error) {
conn, ok := <-l.conns
if !ok {
return nil, fmt.Errorf("listener closed")
}
return conn, nil
}
func (l *Listener) PutConn(conn net.Conn) error {
err := errors.PanicToError(func() {
l.conns <- conn
})
return err
}
func (l *Listener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if !l.closed {
close(l.conns)
l.closed = true
}
return nil
}
func (l *Listener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}
type HttpProxy struct { type HttpProxy struct {
l *Listener l *Listener
s *http.Server s *http.Server

View File

@@ -17,7 +17,10 @@ package plugin
import ( import (
"fmt" "fmt"
"io" "io"
"net"
"sync"
"github.com/fatedier/frp/utils/errors"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
) )
@@ -45,3 +48,44 @@ type Plugin interface {
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
Close() error Close() error
} }
type Listener struct {
conns chan net.Conn
closed bool
mu sync.Mutex
}
func NewProxyListener() *Listener {
return &Listener{
conns: make(chan net.Conn, 64),
}
}
func (l *Listener) Accept() (net.Conn, error) {
conn, ok := <-l.conns
if !ok {
return nil, fmt.Errorf("listener closed")
}
return conn, nil
}
func (l *Listener) PutConn(conn net.Conn) error {
err := errors.PanicToError(func() {
l.conns <- conn
})
return err
}
func (l *Listener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if !l.closed {
close(l.conns)
l.closed = true
}
return nil
}
func (l *Listener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}

View File

@@ -32,13 +32,23 @@ func init() {
type Socks5Plugin struct { type Socks5Plugin struct {
Server *gosocks5.Server Server *gosocks5.Server
user string
passwd string
} }
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) { func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
sp := &Socks5Plugin{} user := params["plugin_user"]
sp.Server, err = gosocks5.New(&gosocks5.Config{ passwd := params["plugin_passwd"]
cfg := &gosocks5.Config{
Logger: log.New(ioutil.Discard, "", log.LstdFlags), Logger: log.New(ioutil.Discard, "", log.LstdFlags),
}) }
if user != "" || passwd != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
}
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(cfg)
p = sp p = sp
return return
} }

View File

@@ -0,0 +1,87 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package plugin
import (
"io"
"net/http"
"github.com/julienschmidt/httprouter"
frpNet "github.com/fatedier/frp/utils/net"
)
const PluginStaticFile = "static_file"
func init() {
Register(PluginStaticFile, NewStaticFilePlugin)
}
type StaticFilePlugin struct {
localPath string
stripPrefix string
httpUser string
httpPasswd string
l *Listener
s *http.Server
}
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
localPath := params["plugin_local_path"]
stripPrefix := params["plugin_strip_prefix"]
httpUser := params["plugin_http_user"]
httpPasswd := params["plugin_http_passwd"]
listener := NewProxyListener()
sp := &StaticFilePlugin{
localPath: localPath,
stripPrefix: stripPrefix,
httpUser: httpUser,
httpPasswd: httpPasswd,
l: listener,
}
var prefix string
if stripPrefix != "" {
prefix = "/" + stripPrefix + "/"
} else {
prefix = "/"
}
router := httprouter.New()
router.Handler("GET", prefix+"*filepath", frpNet.MakeHttpGzipHandler(
frpNet.NewHttpBasicAuthWraper(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))), httpUser, httpPasswd)))
sp.s = &http.Server{
Handler: router,
}
go sp.s.Serve(listener)
return sp, nil
}
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sp.l.PutConn(wrapConn)
}
func (sp *StaticFilePlugin) Name() string {
return PluginStaticFile
}
func (sp *StaticFilePlugin) Close() error {
sp.s.Close()
sp.l.Close()
return nil
}

View File

@@ -82,6 +82,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
mu.Lock() mu.Lock()
delete(udpConnMap, addr) delete(udpConnMap, addr)
mu.Unlock() mu.Unlock()
udpConn.Close()
}() }()
buf := pool.GetBuf(1500) buf := pool.GetBuf(1500)

View File

@@ -14,7 +14,7 @@ make -f ./Makefile.cross-compiles
rm -rf ./packages rm -rf ./packages
mkdir ./packages mkdir ./packages
os_all='linux windows darwin' os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm mips64 mips64le mips mipsle' arch_all='386 amd64 arm mips64 mips64le mips mipsle'
for os in $os_all; do for os in $os_all; do

View File

@@ -20,6 +20,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
@@ -55,6 +56,9 @@ type Control struct {
// pool count // pool count
poolCount int poolCount int
// ports used, for limitations
portsUsedNum int
// last time got the Ping message // last time got the Ping message
lastPing time.Time lastPing time.Time
@@ -84,6 +88,7 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10), workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
proxies: make(map[string]Proxy), proxies: make(map[string]Proxy),
poolCount: loginMsg.PoolCount, poolCount: loginMsg.PoolCount,
portsUsedNum: 0,
lastPing: time.Now(), lastPing: time.Now(),
runId: loginMsg.RunId, runId: loginMsg.RunId,
status: consts.Working, status: consts.Working,
@@ -99,7 +104,7 @@ func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{ loginRespMsg := &msg.LoginResp{
Version: version.Full(), Version: version.Full(),
RunId: ctl.runId, RunId: ctl.runId,
ServerUdpPort: config.ServerCommonCfg.BindUdpPort, ServerUdpPort: g.GlbServerCfg.BindUdpPort,
Error: "", Error: "",
} }
msg.WriteMsg(ctl.conn, loginRespMsg) msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -168,7 +173,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
return return
} }
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second): case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection") err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err) ctl.conn.Warn("%v", err)
return return
@@ -198,7 +203,7 @@ func (ctl *Control) writer() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done() defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken)) encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
if err != nil { if err != nil {
ctl.conn.Error("crypto new writer error: %v", err) ctl.conn.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start() ctl.allShutdown.Start()
@@ -227,7 +232,7 @@ func (ctl *Control) reader() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken)) encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
for { for {
if m, err := msg.ReadMsg(encReader); err != nil { if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF { if err == io.EOF {
@@ -261,13 +266,14 @@ func (ctl *Control) stoper() {
ctl.conn.Close() ctl.conn.Close()
ctl.readerShutdown.WaitDone() ctl.readerShutdown.WaitDone()
ctl.mu.Lock()
defer ctl.mu.Unlock()
close(ctl.workConnCh) close(ctl.workConnCh)
for workConn := range ctl.workConnCh { for workConn := range ctl.workConnCh {
workConn.Close() workConn.Close()
} }
ctl.mu.Lock()
defer ctl.mu.Unlock()
for _, pxy := range ctl.proxies { for _, pxy := range ctl.proxies {
pxy.Close() pxy.Close()
ctl.svr.DelProxy(pxy.GetName()) ctl.svr.DelProxy(pxy.GetName())
@@ -296,9 +302,10 @@ func (ctl *Control) manager() {
for { for {
select { select {
case <-heartbeat.C: case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second { if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout") ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start() ctl.allShutdown.Start()
return
} }
case rawMsg, ok := <-ctl.readCh: case rawMsg, ok := <-ctl.readCh:
if !ok { if !ok {
@@ -336,7 +343,7 @@ func (ctl *Control) manager() {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf var pxyConf config.ProxyConf
// Load configures from NewProxy message and check. // Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConf(pxyMsg) pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
if err != nil { if err != nil {
return return
} }
@@ -348,6 +355,26 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
return remoteAddr, err return remoteAddr, err
} }
// Check ports used number in each client
if g.GlbServerCfg.MaxPortsPerClient > 0 {
ctl.mu.Lock()
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
ctl.mu.Unlock()
err = fmt.Errorf("exceed the max_ports_per_client")
return
}
ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum()
ctl.mu.Unlock()
defer func() {
if err != nil {
ctl.mu.Lock()
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
ctl.mu.Unlock()
}
}()
}
remoteAddr, err = pxy.Run() remoteAddr, err = pxy.Run()
if err != nil { if err != nil {
return return
@@ -371,16 +398,21 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) { func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
ctl.mu.Lock() ctl.mu.Lock()
defer ctl.mu.Unlock()
pxy, ok := ctl.proxies[closeMsg.ProxyName] pxy, ok := ctl.proxies[closeMsg.ProxyName]
if !ok { if !ok {
ctl.mu.Unlock()
return return
} }
if g.GlbServerCfg.MaxPortsPerClient > 0 {
ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
}
pxy.Close() pxy.Close()
ctl.svr.DelProxy(pxy.GetName()) ctl.svr.DelProxy(pxy.GetName())
delete(ctl.proxies, closeMsg.ProxyName) delete(ctl.proxies, closeMsg.ProxyName)
ctl.mu.Unlock()
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType) StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
return return
} }

View File

@@ -21,7 +21,7 @@ import (
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
@@ -36,10 +36,14 @@ func RunDashboardServer(addr string, port int) (err error) {
// url router // url router
router := httprouter.New() router := httprouter.New()
user, passwd := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
// api, see dashboard_api.go // api, see dashboard_api.go
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd)) router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd))
router.GET("/api/proxy/tcp/:name", frpNet.HttprouterBasicAuth(apiProxyTcpByName, user, passwd))
router.GET("/api/proxy/udp/:name", frpNet.HttprouterBasicAuth(apiProxyUdpByName, user, passwd))
router.GET("/api/proxy/http/:name", frpNet.HttprouterBasicAuth(apiProxyHttpByName, user, passwd))
router.GET("/api/proxy/https/:name", frpNet.HttprouterBasicAuth(apiProxyHttpsByName, user, passwd))
router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd)) router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd))
router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd)) router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd))
router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd)) router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))

View File

@@ -18,6 +18,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts" "github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
@@ -60,7 +61,7 @@ func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
}() }()
log.Info("Http request: [/api/serverinfo]") log.Info("Http request: [/api/serverinfo]")
cfg := config.ServerCommonCfg cfg := &g.GlbServerCfg.ServerCommonConf
serverStats := StatsGetServer() serverStats := StatsGetServer()
res = ServerInfoResp{ res = ServerInfoResp{
Version: version.Full(), Version: version.Full(),
@@ -189,6 +190,119 @@ func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
return return
} }
// Get proxy info by name.
type GetProxyStatsResp struct {
GeneralResponse
Name string `json:"name"`
Conf config.ProxyConf `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"`
TodayTrafficOut int64 `json:"today_traffic_out"`
CurConns int64 `json:"cur_conns"`
LastStartTime string `json:"last_start_time"`
LastCloseTime string `json:"last_close_time"`
Status string `json:"status"`
}
// api/proxy/tcp/:name
func apiProxyTcpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/tcp/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/tcp/:name]")
res = getProxyStatsByTypeAndName(consts.TcpProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/udp/:name
func apiProxyUdpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/udp/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/udp/:name]")
res = getProxyStatsByTypeAndName(consts.UdpProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/http/:name
func apiProxyHttpByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/http/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/http/:name]")
res = getProxyStatsByTypeAndName(consts.HttpProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/https/:name
func apiProxyHttpsByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyStatsResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/https/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/https/:name]")
res = getProxyStatsByTypeAndName(consts.HttpsProxy, name)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
func getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
proxyInfo.Name = proxyName
ps := StatsGetProxiesByTypeAndName(proxyType, proxyName)
if ps == nil {
proxyInfo.Code = 1
proxyInfo.Msg = "no proxy info found"
} else {
if pxy, ok := ServerService.pxyManager.GetByName(proxyName); ok {
proxyInfo.Conf = pxy.GetConf()
proxyInfo.Status = consts.Online
} else {
proxyInfo.Status = consts.Offline
}
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
proxyInfo.CurConns = ps.CurConns
proxyInfo.LastStartTime = ps.LastStartTime
proxyInfo.LastCloseTime = ps.LastCloseTime
}
return
}
// api/proxy/traffic/:name // api/proxy/traffic/:name
type GetProxyTrafficResp struct { type GetProxyTrafficResp struct {
GeneralResponse GeneralResponse

View File

@@ -18,7 +18,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/metric" "github.com/fatedier/frp/utils/metric"
) )
@@ -92,19 +92,19 @@ func StatsClearUselessInfo() {
} }
func StatsNewClient() { func StatsNewClient() {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.ClientCounts.Inc(1) globalStats.ClientCounts.Inc(1)
} }
} }
func StatsCloseClient() { func StatsCloseClient() {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.ClientCounts.Dec(1) globalStats.ClientCounts.Dec(1)
} }
} }
func StatsNewProxy(name string, proxyType string) { func StatsNewProxy(name string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.mu.Lock() globalStats.mu.Lock()
defer globalStats.mu.Unlock() defer globalStats.mu.Unlock()
counter, ok := globalStats.ProxyTypeCounts[proxyType] counter, ok := globalStats.ProxyTypeCounts[proxyType]
@@ -130,7 +130,7 @@ func StatsNewProxy(name string, proxyType string) {
} }
func StatsCloseProxy(proxyName string, proxyType string) { func StatsCloseProxy(proxyName string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.mu.Lock() globalStats.mu.Lock()
defer globalStats.mu.Unlock() defer globalStats.mu.Unlock()
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok { if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
@@ -143,7 +143,7 @@ func StatsCloseProxy(proxyName string, proxyType string) {
} }
func StatsOpenConnection(name string) { func StatsOpenConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.CurConns.Inc(1) globalStats.CurConns.Inc(1)
globalStats.mu.Lock() globalStats.mu.Lock()
@@ -157,7 +157,7 @@ func StatsOpenConnection(name string) {
} }
func StatsCloseConnection(name string) { func StatsCloseConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.CurConns.Dec(1) globalStats.CurConns.Dec(1)
globalStats.mu.Lock() globalStats.mu.Lock()
@@ -171,7 +171,7 @@ func StatsCloseConnection(name string) {
} }
func StatsAddTrafficIn(name string, trafficIn int64) { func StatsAddTrafficIn(name string, trafficIn int64) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.TotalTrafficIn.Inc(trafficIn) globalStats.TotalTrafficIn.Inc(trafficIn)
globalStats.mu.Lock() globalStats.mu.Lock()
@@ -186,7 +186,7 @@ func StatsAddTrafficIn(name string, trafficIn int64) {
} }
func StatsAddTrafficOut(name string, trafficOut int64) { func StatsAddTrafficOut(name string, trafficOut int64) {
if config.ServerCommonCfg.DashboardPort != 0 { if g.GlbServerCfg.DashboardPort != 0 {
globalStats.TotalTrafficOut.Inc(trafficOut) globalStats.TotalTrafficOut.Inc(trafficOut)
globalStats.mu.Lock() globalStats.mu.Lock()
@@ -263,6 +263,37 @@ func StatsGetProxiesByType(proxyType string) []*ProxyStats {
return res return res
} }
func StatsGetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
for name, proxyStats := range globalStats.ProxyStatistics {
if proxyStats.ProxyType != proxyType {
continue
}
if name != proxyName {
continue
}
res = &ProxyStats{
Name: name,
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
}
if !proxyStats.LastStartTime.IsZero() {
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
}
if !proxyStats.LastCloseTime.IsZero() {
res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05")
}
break
}
return
}
type ProxyTrafficInfo struct { type ProxyTrafficInfo struct {
Name string Name string
TrafficIn []int64 TrafficIn []int64

View File

@@ -23,6 +23,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp" "github.com/fatedier/frp/models/proto/udp"
@@ -40,15 +41,18 @@ type Proxy interface {
GetName() string GetName() string
GetConf() config.ProxyConf GetConf() config.ProxyConf
GetWorkConnFromPool() (workConn frpNet.Conn, err error) GetWorkConnFromPool() (workConn frpNet.Conn, err error)
GetUsedPortsNum() int
Close() Close()
log.Logger log.Logger
} }
type BaseProxy struct { type BaseProxy struct {
name string name string
ctl *Control ctl *Control
listeners []frpNet.Listener listeners []frpNet.Listener
mu sync.RWMutex usedPortsNum int
mu sync.RWMutex
log.Logger log.Logger
} }
@@ -60,6 +64,10 @@ func (pxy *BaseProxy) GetControl() *Control {
return pxy.ctl return pxy.ctl
} }
func (pxy *BaseProxy) GetUsedPortsNum() int {
return pxy.usedPortsNum
}
func (pxy *BaseProxy) Close() { func (pxy *BaseProxy) Close() {
pxy.Info("proxy closing") pxy.Info("proxy closing")
for _, l := range pxy.listeners { for _, l := range pxy.listeners {
@@ -119,13 +127,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) { func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
basePxy := BaseProxy{ basePxy := BaseProxy{
name: pxyConf.GetName(), name: pxyConf.GetBaseInfo().ProxyName,
ctl: ctl, ctl: ctl,
listeners: make([]frpNet.Listener, 0), listeners: make([]frpNet.Listener, 0),
Logger: log.NewPrefixLogger(ctl.runId), Logger: log.NewPrefixLogger(ctl.runId),
} }
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
basePxy.usedPortsNum = 1
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: basePxy, BaseProxy: basePxy,
cfg: cfg, cfg: cfg,
@@ -141,6 +150,7 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
basePxy.usedPortsNum = 1
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: basePxy, BaseProxy: basePxy,
cfg: cfg, cfg: cfg,
@@ -182,7 +192,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realPort
listener, errRet := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.realPort) listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@@ -235,7 +245,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
} }
tmpDomain := routeConfig.Domain tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(config.ServerCommonCfg.VhostHttpPort))) addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
pxy.closeFuncs = append(pxy.closeFuncs, func() { pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation) pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
}) })
@@ -244,7 +254,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
} }
if pxy.cfg.SubDomain != "" { if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
for _, location := range locations { for _, location := range locations {
routeConfig.Location = location routeConfig.Location = location
err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig) err = pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
@@ -253,7 +263,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
} }
tmpDomain := routeConfig.Domain tmpDomain := routeConfig.Domain
tmpLocation := routeConfig.Location tmpLocation := routeConfig.Location
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(config.ServerCommonCfg.VhostHttpPort))) addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
pxy.closeFuncs = append(pxy.closeFuncs, func() { pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation) pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
}) })
@@ -277,7 +287,7 @@ func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
var rwc io.ReadWriteCloser = tmpConn var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption { if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(config.ServerCommonCfg.PrivilegeToken)) rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
if err != nil { if err != nil {
pxy.Error("create encryption stream error: %v", err) pxy.Error("create encryption stream error: %v", err)
return return
@@ -325,11 +335,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
l.AddLogPrefix(pxy.name) l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l) pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(config.ServerCommonCfg.VhostHttpsPort))) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
} }
if pxy.cfg.SubDomain != "" { if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig) l, errRet := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
@@ -338,7 +348,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
l.AddLogPrefix(pxy.name) l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l) pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(config.ServerCommonCfg.VhostHttpsPort))) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
} }
pxy.startListenHandler(pxy, HandleUserTcpConnection) pxy.startListenHandler(pxy, HandleUserTcpConnection)
@@ -469,7 +479,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realPort
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.realPort)) addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@@ -635,7 +645,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
var local io.ReadWriteCloser = workConn var local io.ReadWriteCloser = workConn
cfg := pxy.GetConf().GetBaseInfo() cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption { if cfg.UseEncryption {
local, err = frpIo.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken)) local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
if err != nil { if err != nil {
pxy.Error("create encryption stream error: %v", err) pxy.Error("create encryption stream error: %v", err)
return return

View File

@@ -21,7 +21,7 @@ import (
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
@@ -29,7 +29,7 @@ import (
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/vhost" "github.com/fatedier/frp/utils/vhost"
"github.com/xtaci/smux" fmux "github.com/hashicorp/yamux"
) )
const ( const (
@@ -71,13 +71,13 @@ type Service struct {
} }
func NewService() (svr *Service, err error) { func NewService() (svr *Service, err error) {
cfg := config.ServerCommonCfg cfg := &g.GlbServerCfg.ServerCommonConf
svr = &Service{ svr = &Service{
ctlManager: NewControlManager(), ctlManager: NewControlManager(),
pxyManager: NewProxyManager(), pxyManager: NewProxyManager(),
visitorManager: NewVisitorManager(), visitorManager: NewVisitorManager(),
tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts), tcpPortManager: NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.PrivilegeAllowPorts), udpPortManager: NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
} }
// Init assets. // Init assets.
@@ -170,7 +170,7 @@ func (svr *Service) Run() {
if svr.natHoleController != nil { if svr.natHoleController != nil {
go svr.natHoleController.Run() go svr.natHoleController.Run()
} }
if config.ServerCommonCfg.KcpBindPort > 0 { if g.GlbServerCfg.KcpBindPort > 0 {
go svr.HandleListener(svr.kcpListener) go svr.HandleListener(svr.kcpListener)
} }
svr.HandleListener(svr.listener) svr.HandleListener(svr.listener)
@@ -233,8 +233,8 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
} }
} }
if config.ServerCommonCfg.TcpMux { if g.GlbServerCfg.TcpMux {
session, err := smux.Server(frpConn, nil) session, err := fmux.Server(frpConn, nil)
if err != nil { if err != nil {
log.Warn("Failed to create mux connection: %v", err) log.Warn("Failed to create mux connection: %v", err)
frpConn.Close() frpConn.Close()
@@ -270,11 +270,11 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
// Check auth. // Check auth.
nowTime := time.Now().Unix() nowTime := time.Now().Unix()
if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout { if g.GlbServerCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > g.GlbServerCfg.AuthTimeout {
err = fmt.Errorf("authorization timeout") err = fmt.Errorf("authorization timeout")
return return
} }
if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey { if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
err = fmt.Errorf("authorization failed") err = fmt.Errorf("authorization failed")
return return
} }

View File

@@ -4,7 +4,7 @@ server_port = 10700
log_file = ./frpc.log log_file = ./frpc.log
# debug, info, warn, error # debug, info, warn, error
log_level = debug log_level = debug
privilege_token = 123456 token = 123456
admin_port = 10600 admin_port = 10600
admin_user = abc admin_user = abc
admin_pwd = abc admin_pwd = abc
@@ -161,3 +161,9 @@ remote_port = 0
type = tcp type = tcp
plugin = http_proxy plugin = http_proxy
remote_port = 0 remote_port = 0
[range:range_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 30000-30001,30003
remote_port = 30000-30001,30003

View File

@@ -4,7 +4,7 @@ server_port = 10700
log_file = ./frpc_visitor.log log_file = ./frpc_visitor.log
# debug, info, warn, error # debug, info, warn, error
log_level = debug log_level = debug
privilege_token = 123456 token = 123456
[stcp_visitor] [stcp_visitor]
type = stcp type = stcp

View File

@@ -4,6 +4,6 @@ bind_port = 10700
vhost_http_port = 10804 vhost_http_port = 10804
log_file = ./frps.log log_file = ./frps.log
log_level = debug log_level = debug
privilege_token = 123456 token = 123456
privilege_allow_ports = 10000-20000,20002,30000-40000 allow_ports = 10000-20000,20002,30000-50000
subdomain_host = sub.com subdomain_host = sub.com

View File

@@ -53,8 +53,9 @@ var (
ProxyUdpPortNotAllowed string = "udp_port_not_allowed" ProxyUdpPortNotAllowed string = "udp_port_not_allowed"
ProxyUdpPortNormal string = "udp_port_normal" ProxyUdpPortNormal string = "udp_port_normal"
ProxyUdpRandomPort string = "udp_random_port" ProxyUdpRandomPort string = "udp_random_port"
ProxyHttpProxy string = "http_proxy"
ProxyHttpProxy string = "http_proxy" ProxyRangeTcpPrefix string = "range_tcp"
) )
func init() { func init() {
@@ -208,7 +209,7 @@ func TestWebSocket(t *testing.T) {
assert.Equal(TEST_HTTP_NORMAL_STR, string(msg)) assert.Equal(TEST_HTTP_NORMAL_STR, string(msg))
} }
func TestPrivilegeAllowPorts(t *testing.T) { func TestAllowPorts(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// Port not allowed // Port not allowed
status, err := getProxyStatus(ProxyTcpPortNotAllowed) status, err := getProxyStatus(ProxyTcpPortNotAllowed)
@@ -286,3 +287,15 @@ func TestPluginHttpProxy(t *testing.T) {
} }
} }
} }
func TestRangePortsMapping(t *testing.T) {
assert := assert.New(t)
for i := 0; i < 3; i++ {
name := fmt.Sprintf("%s_%d", ProxyRangeTcpPrefix, i)
status, err := getProxyStatus(name)
if assert.NoError(err) {
assert.Equal(client.ProxyStatusRunning, status.Status)
}
}
}

View File

@@ -21,11 +21,12 @@ import (
"io" "io"
"net" "net"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
kcp "github.com/xtaci/kcp-go" kcp "github.com/fatedier/kcp-go"
) )
// Conn is the interface of connections used in frp. // Conn is the interface of connections used in frp.
@@ -178,6 +179,7 @@ func (sc *SharedConn) WriteBuff(buffer []byte) (err error) {
type StatsConn struct { type StatsConn struct {
Conn Conn
closed int64 // 1 means closed
totalRead int64 totalRead int64
totalWrite int64 totalWrite int64
statsFunc func(totalRead, totalWrite int64) statsFunc func(totalRead, totalWrite int64)
@@ -203,9 +205,12 @@ func (statsConn *StatsConn) Write(p []byte) (n int, err error) {
} }
func (statsConn *StatsConn) Close() (err error) { func (statsConn *StatsConn) Close() (err error) {
err = statsConn.Conn.Close() old := atomic.SwapInt64(&statsConn.closed, 1)
if statsConn.statsFunc != nil { if old != 1 {
statsConn.statsFunc(statsConn.totalRead, statsConn.totalWrite) err = statsConn.Conn.Close()
if statsConn.statsFunc != nil {
statsConn.statsFunc(statsConn.totalRead, statsConn.totalWrite)
}
} }
return return
} }

View File

@@ -19,6 +19,8 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"strconv"
"strings"
) )
// RandId return a rand string used in frp. // RandId return a rand string used in frp.
@@ -54,3 +56,48 @@ func CanonicalAddr(host string, port int) (addr string) {
} }
return return
} }
func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
rangeStr = strings.TrimSpace(rangeStr)
numbers = make([]int64, 0)
// e.g. 1000-2000,2001,2002,3000-4000
numRanges := strings.Split(rangeStr, ",")
for _, numRangeStr := range numRanges {
// 1000-2000 or 2001
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(numArray)
if rangeType == 1 {
// single number
singleNum, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
numbers = append(numbers, singleNum)
} else if rangeType == 2 {
// range numbers
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet)
return
}
if max < min {
err = fmt.Errorf("range number is invalid")
return
}
for i := min; i <= max; i++ {
numbers = append(numbers, i)
}
} else {
err = fmt.Errorf("range number is invalid")
return
}
}
return
}

View File

@@ -20,3 +20,29 @@ func TestGetAuthKey(t *testing.T) {
t.Log(key) t.Log(key)
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key) assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
} }
func TestParseRangeNumbers(t *testing.T) {
assert := assert.New(t)
numbers, err := ParseRangeNumbers("2-5")
if assert.NoError(err) {
assert.Equal([]int64{2, 3, 4, 5}, numbers)
}
numbers, err = ParseRangeNumbers("1")
if assert.NoError(err) {
assert.Equal([]int64{1}, numbers)
}
numbers, err = ParseRangeNumbers("3-5,8")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8}, numbers)
}
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
if assert.NoError(err) {
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
}
_, err = ParseRangeNumbers("3-a")
assert.Error(err)
}

View File

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

View File

@@ -108,7 +108,7 @@ func readHandshake(rd io.Reader) (host string, err error) {
return return
} }
if len(data) < 2 { if len(data) < 2 {
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short") err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short", len(data))
return return
} }

View File

@@ -1,31 +0,0 @@
# Travis CI (http://travis-ci.org/) is a continuous integration
# service for open source projects. This file configures it
# to run unit tests for docopt-go.
language: go
go:
- 1.4
- 1.5
- tip
matrix:
fast_finish: true
before_install:
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/cover
- go get github.com/golang/lint/golint
- go get github.com/mattn/goveralls
install:
- go get -d -v ./... && go build -v ./...
script:
- go vet -x ./...
- $HOME/gopath/bin/golint ./...
- go test -v ./...
- go test -covermode=count -coverprofile=profile.cov .
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 Keith Batten
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,88 +0,0 @@
docopt-go
=========
[![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go)
[![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go)
[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go)
An implementation of [docopt](http://docopt.org/) in the
[Go](http://golang.org/) programming language.
**docopt** helps you create *beautiful* command-line interfaces easily:
```go
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)
}
```
**docopt** parses command-line arguments based on a help message. Don't
write parser code: a good help message already has all the necessary
information in it.
## Installation
⚠ Use the alias “docopt-go”. To use docopt in your Go code:
```go
import "github.com/docopt/docopt-go"
```
To install docopt according to your `$GOPATH`:
```console
$ go get github.com/docopt/docopt-go
```
## API
```go
func Parse(doc string, argv []string, help bool, version string,
optionsFirst bool, exit ...bool) (map[string]interface{}, error)
```
Parse `argv` based on the command-line interface described in `doc`.
Given a conventional command-line help message, docopt creates a parser and
processes the arguments. See
https://github.com/docopt/docopt#help-message-format for a description of the
help message format. If `argv` is `nil`, `os.Args[1:]` is used.
docopt returns a map of option names to the values parsed from `argv`, and an
error or `nil`.
More documentation for docopt is available at
[GoDoc.org](https://godoc.org/github.com/docopt/docopt.go).
## Testing
All tests from the Python version are implemented and passing
at [Travis CI](https://travis-ci.org/docopt/docopt.go). New
language-agnostic tests have been added
to [test_golang.docopt](test_golang.docopt).
To run tests for docopt-go, use `go test`.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,26 +0,0 @@
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

@@ -1,76 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,37 +0,0 @@
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)
}

View File

@@ -1,108 +0,0 @@
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

@@ -1,34 +0,0 @@
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

@@ -1,28 +0,0 @@
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

@@ -1,28 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,43 +0,0 @@
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)
}

View File

@@ -1,24 +0,0 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Example of program which uses [options] shortcut in pattern.
Usage:
options_shortcut_example [options] <port>
Options:
-h --help show this help message and exit
--version show version and exit
-n, --number N use N as a number
-t, --timeout TIMEOUT set timeout TIMEOUT seconds
--apply apply changes to database
-q operate in quiet mode`
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
fmt.Println(arguments)
}

View File

@@ -1,16 +0,0 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Usage:
quick_example tcp <host> <port> [--timeout=<seconds>]
quick_example serial <port> [--baud=9600] [--timeout=<seconds>]
quick_example -h | --help | --version`
arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
fmt.Println(arguments)
}

View File

@@ -1,31 +0,0 @@
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `usage: foo [-x] [-y]`
arguments, err := docopt.Parse(usage, nil, true, "", false)
if err != nil {
fmt.Println(err)
}
fmt.Println(arguments)
var x = arguments["-x"].(bool) // type assertion required
if x == true {
fmt.Println("x is true")
}
y := arguments["-y"] // no type assertion needed
if y == true {
fmt.Println("y is true")
}
y2 := arguments["-y"]
if y2 == 10 { // this will never be true, a type assertion would have produced a build error
fmt.Println("y is 10")
}
}

View File

@@ -1,9 +0,0 @@
r"""usage: prog [NAME_-2]..."""
$ prog 10 20
{"NAME_-2": ["10", "20"]}
$ prog 10
{"NAME_-2": ["10"]}
$ prog
{"NAME_-2": []}

View File

@@ -1,957 +0,0 @@
r"""Usage: prog
"""
$ prog
{}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -a All.
"""
$ prog
{"-a": false}
$ prog -a
{"-a": true}
$ prog -x
"user-error"
r"""Usage: prog [options]
Options: --all All.
"""
$ prog
{"--all": false}
$ prog --all
{"--all": true}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -v, --verbose Verbose.
"""
$ prog --verbose
{"--verbose": true}
$ prog --ver
{"--verbose": true}
$ prog -v
{"--verbose": true}
r"""Usage: prog [options]
Options: -p PATH
"""
$ prog -p home/
{"-p": "home/"}
$ prog -phome/
{"-p": "home/"}
$ prog -p
"user-error"
r"""Usage: prog [options]
Options: --path <path>
"""
$ prog --path home/
{"--path": "home/"}
$ prog --path=home/
{"--path": "home/"}
$ prog --pa home/
{"--path": "home/"}
$ prog --pa=home/
{"--path": "home/"}
$ prog --path
"user-error"
r"""Usage: prog [options]
Options: -p PATH, --path=<path> Path to files.
"""
$ prog -proot
{"--path": "root"}
r"""Usage: prog [options]
Options: -p --path PATH Path to files.
"""
$ prog -p root
{"--path": "root"}
$ prog --path root
{"--path": "root"}
r"""Usage: prog [options]
Options:
-p PATH Path to files [default: ./]
"""
$ prog
{"-p": "./"}
$ prog -phome
{"-p": "home"}
r"""UsAgE: prog [options]
OpTiOnS: --path=<files> Path to files
[dEfAuLt: /root]
"""
$ prog
{"--path": "/root"}
$ prog --path=home
{"--path": "home"}
r"""usage: prog [options]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
$ prog -a -r
{"-a": true,
"-r": true,
"-m": null}
r"""Usage: prog [options]
Options: --version
--verbose
"""
$ prog --version
{"--version": true,
"--verbose": false}
$ prog --verbose
{"--version": false,
"--verbose": true}
$ prog --ver
"user-error"
$ prog --verb
{"--version": false,
"--verbose": true}
r"""usage: prog [-a -r -m <msg>]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
r"""usage: prog [-armmsg]
options: -a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
r"""usage: prog -a -b
options:
-a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog (-a -b)
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog [-a] -b
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
{"-a": false, "-b": true}
$ prog
"user-error"
r"""usage: prog [(-a -b)]
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
"user-error"
$ prog
{"-a": false, "-b": false}
r"""usage: prog (-a|-b)
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
"user-error"
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog [ -a | -b ]
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
{"-a": false, "-b": false}
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog <arg>"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog [<arg>]"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
{"<arg>": null}
r"""usage: prog <kind> <name> <type>"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog <kind> [<name> <type>]"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
{"<kind>": "10", "<name>": "20", "<type>": null}
$ prog
"user-error"
r"""usage: prog [<kind> | <name> <type>]"""
$ prog 10 20 40
"user-error"
$ prog 20 40
{"<kind>": null, "<name>": "20", "<type>": "40"}
$ prog
{"<kind>": null, "<name>": null, "<type>": null}
r"""usage: prog (<kind> --all | <name>)
options:
--all
"""
$ prog 10 --all
{"<kind>": "10", "--all": true, "<name>": null}
$ prog 10
{"<kind>": null, "--all": false, "<name>": "10"}
$ prog
"user-error"
r"""usage: prog [<name> <name>]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
{"<name>": ["10"]}
$ prog
{"<name>": []}
r"""usage: prog [(<name> <name>)]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
"user-error"
$ prog
{"<name>": []}
r"""usage: prog NAME..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
"user-error"
r"""usage: prog [NAME]..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME...]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME [NAME ...]]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog (NAME | --foo NAME)
options: --foo
"""
$ prog 10
{"NAME": "10", "--foo": false}
$ prog --foo 10
{"NAME": "10", "--foo": true}
$ prog --foo=10
"user-error"
r"""usage: prog (NAME | --foo) [--bar | NAME]
options: --foo
options: --bar
"""
$ prog 10
{"NAME": ["10"], "--foo": false, "--bar": false}
$ prog 10 20
{"NAME": ["10", "20"], "--foo": false, "--bar": false}
$ prog --foo --bar
{"NAME": [], "--foo": true, "--bar": true}
r"""Naval Fate.
Usage:
prog ship new <name>...
prog ship [<name>] move <x> <y> [--speed=<kn>]
prog ship shoot <x> <y>
prog mine (set|remove) <x> <y> [--moored|--drifting]
prog -h | --help
prog --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Mored (anchored) mine.
--drifting Drifting mine.
"""
$ prog ship Guardian move 150 300 --speed=20
{"--drifting": false,
"--help": false,
"--moored": false,
"--speed": "20",
"--version": false,
"<name>": ["Guardian"],
"<x>": "150",
"<y>": "300",
"mine": false,
"move": true,
"new": false,
"remove": false,
"set": false,
"ship": true,
"shoot": false}
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: prog --aabb | --aa"""
$ prog --aa
{"--aabb": false, "--aa": true}
$ prog --a
"user-error" # not a unique prefix
#
# Counting number of flags
#
r"""Usage: prog -v"""
$ prog -v
{"-v": true}
r"""Usage: prog [-v -v]"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
r"""Usage: prog -v ..."""
$ prog
"user-error"
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvvvv
{"-v": 6}
r"""Usage: prog [-v | -vv | -vvv]
This one is probably most readable user-friednly variant.
"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvv
"user-error"
r"""usage: prog [--ver --ver]"""
$ prog --ver --ver
{"--ver": 2}
#
# Counting commands
#
r"""usage: prog [go]"""
$ prog go
{"go": true}
r"""usage: prog [go go]"""
$ prog
{"go": 0}
$ prog go
{"go": 1}
$ prog go go
{"go": 2}
$ prog go go go
"user-error"
r"""usage: prog go..."""
$ prog go go go go go
{"go": 5}
#
# [options] does not include options from usage-pattern
#
r"""usage: prog [options] [-a]
options: -a
-b
"""
$ prog -a
{"-a": true, "-b": false}
$ prog -aa
"user-error"
#
# Test [options] shourtcut
#
r"""Usage: prog [options] A
Options:
-q Be quiet
-v Be verbose.
"""
$ prog arg
{"A": "arg", "-v": false, "-q": false}
$ prog -v arg
{"A": "arg", "-v": true, "-q": false}
$ prog -q arg
{"A": "arg", "-v": false, "-q": true}
#
# Test single dash
#
r"""usage: prog [-]"""
$ prog -
{"-": true}
$ prog
{"-": false}
#
# If argument is repeated, its value should always be a list
#
r"""usage: prog [NAME [NAME ...]]"""
$ prog a b
{"NAME": ["a", "b"]}
$ prog
{"NAME": []}
#
# Option's argument defaults to null/None
#
r"""usage: prog [options]
options:
-a Add
-m <msg> Message
"""
$ prog -a
{"-m": null, "-a": true}
#
# Test options without description
#
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: git [-v | --verbose]"""
$ prog -v
{"-v": true, "--verbose": false}
r"""usage: git remote [-v | --verbose]"""
$ prog remote -v
{"remote": true, "-v": true, "--verbose": false}
#
# Test empty usage pattern
#
r"""usage: prog"""
$ prog
{}
r"""usage: prog
prog <a> <b>
"""
$ prog 1 2
{"<a>": "1", "<b>": "2"}
$ prog
{"<a>": null, "<b>": null}
r"""usage: prog <a> <b>
prog
"""
$ prog
{"<a>": null, "<b>": null}
#
# Option's argument should not capture default value from usage pattern
#
r"""usage: prog [--file=<f>]"""
$ prog
{"--file": null}
r"""usage: prog [--file=<f>]
options: --file <a>
"""
$ prog
{"--file": null}
r"""Usage: prog [-a <host:port>]
Options: -a, --address <host:port> TCP address [default: localhost:6283].
"""
$ prog
{"--address": "localhost:6283"}
#
# If option with argument could be repeated,
# its arguments should be accumulated into a list
#
r"""usage: prog --long=<arg> ..."""
$ prog --long one
{"--long": ["one"]}
$ prog --long one --long two
{"--long": ["one", "two"]}
#
# Test multiple elements repeated at once
#
r"""usage: prog (go <direction> --speed=<km/h>)..."""
$ prog go left --speed=5 go right --speed=9
{"go": 2, "<direction>": ["left", "right"], "--speed": ["5", "9"]}
#
# Required options should work with option shortcut
#
r"""usage: prog [options] -a
options: -a
"""
$ prog -a
{"-a": true}
#
# If option could be repeated its defaults should be split into a list
#
r"""usage: prog [-o <o>]...
options: -o <o> [default: x]
"""
$ prog -o this -o that
{"-o": ["this", "that"]}
$ prog
{"-o": ["x"]}
r"""usage: prog [-o <o>]...
options: -o <o> [default: x y]
"""
$ prog -o this
{"-o": ["this"]}
$ prog
{"-o": ["x", "y"]}
#
# Test stacked option's argument
#
r"""usage: prog -pPATH
options: -p PATH
"""
$ prog -pHOME
{"-p": "HOME"}
#
# Issue 56: Repeated mutually exclusive args give nested lists sometimes
#
r"""Usage: foo (--xx=x|--yy=y)..."""
$ prog --xx=1 --yy=2
{"--xx": ["1"], "--yy": ["2"]}
#
# POSIXly correct tokenization
#
r"""usage: prog [<input file>]"""
$ prog f.txt
{"<input file>": "f.txt"}
r"""usage: prog [--input=<file name>]..."""
$ prog --input a.txt --input=b.txt
{"--input": ["a.txt", "b.txt"]}
#
# Issue 85: `[options]` shourtcut with multiple subcommands
#
r"""usage: prog good [options]
prog fail [options]
options: --loglevel=N
"""
$ prog fail --loglevel 5
{"--loglevel": "5", "fail": true, "good": false}
#
# Usage-section syntax
#
r"""usage:prog --foo"""
$ prog --foo
{"--foo": true}
r"""PROGRAM USAGE: prog --foo"""
$ prog --foo
{"--foo": true}
r"""Usage: prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
#
# Options-section syntax
#
r"""Usage: prog [options]
global options: --foo
local options: --baz
--bar
other options:
--egg
--spam
-not-an-option-
"""
$ prog --baz --egg
{"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false}

View File

@@ -8,7 +8,6 @@ matrix:
- go: 1.6 - go: 1.6
- go: 1.7 - go: 1.7
- go: 1.8 - go: 1.8
- go: 1.9
- go: tip - go: tip
allow_failures: allow_failures:
- go: tip - go: tip

View File

@@ -5,8 +5,10 @@
package websocket package websocket
import ( import (
"bufio"
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/base64"
"errors" "errors"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -86,6 +88,50 @@ type Dialer struct {
var errMalformedURL = errors.New("malformed ws or wss URL") var errMalformedURL = errors.New("malformed ws or wss URL")
// parseURL parses the URL.
//
// This function is a replacement for the standard library url.Parse function.
// In Go 1.4 and earlier, url.Parse loses information from the path.
func parseURL(s string) (*url.URL, error) {
// From the RFC:
//
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
var u url.URL
switch {
case strings.HasPrefix(s, "ws://"):
u.Scheme = "ws"
s = s[len("ws://"):]
case strings.HasPrefix(s, "wss://"):
u.Scheme = "wss"
s = s[len("wss://"):]
default:
return nil, errMalformedURL
}
if i := strings.Index(s, "?"); i >= 0 {
u.RawQuery = s[i+1:]
s = s[:i]
}
if i := strings.Index(s, "/"); i >= 0 {
u.Opaque = s[i:]
s = s[:i]
} else {
u.Opaque = "/"
}
u.Host = s
if strings.Contains(u.Host, "@") {
// Don't bother parsing user information because user information is
// not allowed in websocket URIs.
return nil, errMalformedURL
}
return &u, nil
}
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
hostPort = u.Host hostPort = u.Host
hostNoPort = u.Host hostNoPort = u.Host
@@ -104,7 +150,7 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
return hostPort, hostNoPort return hostPort, hostNoPort
} }
// DefaultDialer is a dialer with all fields set to the default values. // DefaultDialer is a dialer with all fields set to the default zero values.
var DefaultDialer = &Dialer{ var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
} }
@@ -131,7 +177,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
return nil, nil, err return nil, nil, err
} }
u, err := url.Parse(urlStr) u, err := parseURL(urlStr)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -200,52 +246,36 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
} }
hostPort, hostNoPort := hostPortNoPort(u)
var proxyURL *url.URL
// Check wether the proxy method has been configured
if d.Proxy != nil {
proxyURL, err = d.Proxy(req)
}
if err != nil {
return nil, nil, err
}
var targetHostPort string
if proxyURL != nil {
targetHostPort, _ = hostPortNoPort(proxyURL)
} else {
targetHostPort = hostPort
}
var deadline time.Time var deadline time.Time
if d.HandshakeTimeout != 0 { if d.HandshakeTimeout != 0 {
deadline = time.Now().Add(d.HandshakeTimeout) deadline = time.Now().Add(d.HandshakeTimeout)
} }
// Get network dial function.
netDial := d.NetDial netDial := d.NetDial
if netDial == nil { if netDial == nil {
netDialer := &net.Dialer{Deadline: deadline} netDialer := &net.Dialer{Deadline: deadline}
netDial = netDialer.Dial netDial = netDialer.Dial
} }
// If needed, wrap the dial function to set the connection deadline. netConn, err := netDial("tcp", targetHostPort)
if !deadline.Equal(time.Time{}) {
forwardDial := netDial
netDial = func(network, addr string) (net.Conn, error) {
c, err := forwardDial(network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
// If needed, wrap the dial function to connect through a proxy.
if d.Proxy != nil {
proxyURL, err := d.Proxy(req)
if err != nil {
return nil, nil, err
}
if proxyURL != nil {
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
if err != nil {
return nil, nil, err
}
netDial = dialer.Dial
}
}
hostPort, hostNoPort := hostPortNoPort(u)
netConn, err := netDial("tcp", hostPort)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -256,6 +286,42 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
} }
}() }()
if err := netConn.SetDeadline(deadline); err != nil {
return nil, nil, err
}
if proxyURL != nil {
connectHeader := make(http.Header)
if user := proxyURL.User; user != nil {
proxyUser := user.Username()
if proxyPassword, passwordSet := user.Password(); passwordSet {
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
}
}
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: hostPort},
Host: hostPort,
Header: connectHeader,
}
connectReq.Write(netConn)
// Read response.
// Okay to use and discard buffered reader here, because
// TLS server will not speak until spoken to.
br := bufio.NewReader(netConn)
resp, err := http.ReadResponse(br, connectReq)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != 200 {
f := strings.SplitN(resp.Status, " ", 2)
return nil, nil, errors.New(f[1])
}
}
if u.Scheme == "https" { if u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig) cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" { if cfg.ServerName == "" {

View File

@@ -5,14 +5,11 @@
package websocket package websocket
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/binary"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/http/httptest" "net/http/httptest"
@@ -34,10 +31,9 @@ var cstUpgrader = Upgrader{
} }
var cstDialer = Dialer{ var cstDialer = Dialer{
Subprotocols: []string{"p1", "p2"}, Subprotocols: []string{"p1", "p2"},
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
HandshakeTimeout: 30 * time.Second,
} }
type cstHandler struct{ *testing.T } type cstHandler struct{ *testing.T }
@@ -147,9 +143,8 @@ func TestProxyDial(t *testing.T) {
s := newServer(t) s := newServer(t)
defer s.Close() defer s.Close()
surl, _ := url.Parse(s.Server.URL) surl, _ := url.Parse(s.URL)
cstDialer := cstDialer // make local copy for modification on next line.
cstDialer.Proxy = http.ProxyURL(surl) cstDialer.Proxy = http.ProxyURL(surl)
connect := false connect := false
@@ -165,8 +160,8 @@ func TestProxyDial(t *testing.T) {
} }
if !connect { if !connect {
t.Log("connect not received") t.Log("connect not recieved")
http.Error(w, "connect not received", 405) http.Error(w, "connect not recieved", 405)
return return
} }
origHandler.ServeHTTP(w, r) origHandler.ServeHTTP(w, r)
@@ -178,16 +173,16 @@ func TestProxyDial(t *testing.T) {
} }
defer ws.Close() defer ws.Close()
sendRecv(t, ws) sendRecv(t, ws)
cstDialer.Proxy = http.ProxyFromEnvironment
} }
func TestProxyAuthorizationDial(t *testing.T) { func TestProxyAuthorizationDial(t *testing.T) {
s := newServer(t) s := newServer(t)
defer s.Close() defer s.Close()
surl, _ := url.Parse(s.Server.URL) surl, _ := url.Parse(s.URL)
surl.User = url.UserPassword("username", "password") surl.User = url.UserPassword("username", "password")
cstDialer := cstDialer // make local copy for modification on next line.
cstDialer.Proxy = http.ProxyURL(surl) cstDialer.Proxy = http.ProxyURL(surl)
connect := false connect := false
@@ -205,8 +200,8 @@ func TestProxyAuthorizationDial(t *testing.T) {
} }
if !connect { if !connect {
t.Log("connect with proxy authorization not received") t.Log("connect with proxy authorization not recieved")
http.Error(w, "connect with proxy authorization not received", 405) http.Error(w, "connect with proxy authorization not recieved", 405)
return return
} }
origHandler.ServeHTTP(w, r) origHandler.ServeHTTP(w, r)
@@ -218,6 +213,8 @@ func TestProxyAuthorizationDial(t *testing.T) {
} }
defer ws.Close() defer ws.Close()
sendRecv(t, ws) sendRecv(t, ws)
cstDialer.Proxy = http.ProxyFromEnvironment
} }
func TestDial(t *testing.T) { func TestDial(t *testing.T) {
@@ -240,7 +237,7 @@ func TestDialCookieJar(t *testing.T) {
d := cstDialer d := cstDialer
d.Jar = jar d.Jar = jar
u, _ := url.Parse(s.URL) u, _ := parseURL(s.URL)
switch u.Scheme { switch u.Scheme {
case "ws": case "ws":
@@ -249,7 +246,7 @@ func TestDialCookieJar(t *testing.T) {
u.Scheme = "https" u.Scheme = "https"
} }
cookies := []*http.Cookie{{Name: "gorilla", Value: "ws", Path: "/"}} cookies := []*http.Cookie{&http.Cookie{Name: "gorilla", Value: "ws", Path: "/"}}
d.Jar.SetCookies(u, cookies) d.Jar.SetCookies(u, cookies)
ws, _, err := d.Dial(s.URL, nil) ws, _, err := d.Dial(s.URL, nil)
@@ -401,17 +398,9 @@ func TestBadMethod(t *testing.T) {
})) }))
defer s.Close() defer s.Close()
req, err := http.NewRequest("POST", s.URL, strings.NewReader("")) resp, err := http.PostForm(s.URL, url.Values{})
if err != nil { if err != nil {
t.Fatalf("NewRequest returned error %v", err) t.Fatalf("PostForm returned error %v", err)
}
req.Header.Set("Connection", "upgrade")
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Sec-Websocket-Version", "13")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Do returned error %v", err)
} }
resp.Body.Close() resp.Body.Close()
if resp.StatusCode != http.StatusMethodNotAllowed { if resp.StatusCode != http.StatusMethodNotAllowed {
@@ -521,82 +510,3 @@ func TestDialCompression(t *testing.T) {
defer ws.Close() defer ws.Close()
sendRecv(t, ws) sendRecv(t, ws)
} }
func TestSocksProxyDial(t *testing.T) {
s := newServer(t)
defer s.Close()
proxyListener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen failed: %v", err)
}
defer proxyListener.Close()
go func() {
c1, err := proxyListener.Accept()
if err != nil {
t.Errorf("proxy accept failed: %v", err)
return
}
defer c1.Close()
c1.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 32)
if _, err := io.ReadFull(c1, buf[:3]); err != nil {
t.Errorf("read failed: %v", err)
return
}
if want := []byte{5, 1, 0}; !bytes.Equal(want, buf[:len(want)]) {
t.Errorf("read %x, want %x", buf[:len(want)], want)
}
if _, err := c1.Write([]byte{5, 0}); err != nil {
t.Errorf("write failed: %v", err)
return
}
if _, err := io.ReadFull(c1, buf[:10]); err != nil {
t.Errorf("read failed: %v", err)
return
}
if want := []byte{5, 1, 0, 1}; !bytes.Equal(want, buf[:len(want)]) {
t.Errorf("read %x, want %x", buf[:len(want)], want)
return
}
buf[1] = 0
if _, err := c1.Write(buf[:10]); err != nil {
t.Errorf("write failed: %v", err)
return
}
ip := net.IP(buf[4:8])
port := binary.BigEndian.Uint16(buf[8:10])
c2, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: int(port)})
if err != nil {
t.Errorf("dial failed; %v", err)
return
}
defer c2.Close()
done := make(chan struct{})
go func() {
io.Copy(c1, c2)
close(done)
}()
io.Copy(c2, c1)
<-done
}()
purl, err := url.Parse("socks5://" + proxyListener.Addr().String())
if err != nil {
t.Fatalf("parse failed: %v", err)
}
cstDialer := cstDialer // make local copy for modification on next line.
cstDialer.Proxy = http.ProxyURL(purl)
ws, _, err := cstDialer.Dial(s.URL, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer ws.Close()
sendRecv(t, ws)
}

View File

@@ -6,9 +6,49 @@ package websocket
import ( import (
"net/url" "net/url"
"reflect"
"testing" "testing"
) )
var parseURLTests = []struct {
s string
u *url.URL
rui string
}{
{"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
{"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
{"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"},
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"},
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"},
{"ss://example.com/a/b", nil, ""},
{"ws://webmaster@example.com/", nil, ""},
{"wss://example.com/a/b?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b", RawQuery: "x=y"}, "/a/b?x=y"},
{"wss://example.com?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/", RawQuery: "x=y"}, "/?x=y"},
}
func TestParseURL(t *testing.T) {
for _, tt := range parseURLTests {
u, err := parseURL(tt.s)
if tt.u != nil && err != nil {
t.Errorf("parseURL(%q) returned error %v", tt.s, err)
continue
}
if tt.u == nil {
if err == nil {
t.Errorf("parseURL(%q) did not return error", tt.s)
}
continue
}
if !reflect.DeepEqual(u, tt.u) {
t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u)
continue
}
if u.RequestURI() != tt.rui {
t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui)
}
}
}
var hostPortNoPortTests = []struct { var hostPortNoPortTests = []struct {
u *url.URL u *url.URL
hostPort, hostNoPort string hostPort, hostNoPort string

View File

@@ -76,7 +76,7 @@ const (
// is UTF-8 encoded text. // is UTF-8 encoded text.
PingMessage = 9 PingMessage = 9
// PongMessage denotes a pong control message. The optional message payload // PongMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text. // is UTF-8 encoded text.
PongMessage = 10 PongMessage = 10
) )
@@ -100,8 +100,9 @@ func (e *netError) Error() string { return e.msg }
func (e *netError) Temporary() bool { return e.temporary } func (e *netError) Temporary() bool { return e.temporary }
func (e *netError) Timeout() bool { return e.timeout } func (e *netError) Timeout() bool { return e.timeout }
// CloseError represents a close message. // CloseError represents close frame.
type CloseError struct { type CloseError struct {
// Code is defined in RFC 6455, section 11.7. // Code is defined in RFC 6455, section 11.7.
Code int Code int
@@ -342,8 +343,7 @@ func (c *Conn) Subprotocol() string {
return c.subprotocol return c.subprotocol
} }
// Close closes the underlying network connection without sending or waiting // Close closes the underlying network connection without sending or waiting for a close frame.
// for a close message.
func (c *Conn) Close() error { func (c *Conn) Close() error {
return c.conn.Close() return c.conn.Close()
} }
@@ -484,9 +484,6 @@ func (c *Conn) prepWrite(messageType int) error {
// //
// There can be at most one open writer on a connection. NextWriter closes the // There can be at most one open writer on a connection. NextWriter closes the
// previous writer if the application has not already done so. // previous writer if the application has not already done so.
//
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
// PongMessage) are supported.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
if err := c.prepWrite(messageType); err != nil { if err := c.prepWrite(messageType); err != nil {
return nil, err return nil, err
@@ -767,6 +764,7 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
// Read methods // Read methods
func (c *Conn) advanceFrame() (int, error) { func (c *Conn) advanceFrame() (int, error) {
// 1. Skip remainder of previous frame. // 1. Skip remainder of previous frame.
if c.readRemaining > 0 { if c.readRemaining > 0 {
@@ -1035,7 +1033,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
} }
// SetReadLimit sets the maximum size for a message read from the peer. If a // SetReadLimit sets the maximum size for a message read from the peer. If a
// message exceeds the limit, the connection sends a close message to the peer // message exceeds the limit, the connection sends a close frame to the peer
// and returns ErrReadLimit to the application. // and returns ErrReadLimit to the application.
func (c *Conn) SetReadLimit(limit int64) { func (c *Conn) SetReadLimit(limit int64) {
c.readLimit = limit c.readLimit = limit
@@ -1048,21 +1046,24 @@ func (c *Conn) CloseHandler() func(code int, text string) error {
// SetCloseHandler sets the handler for close messages received from the peer. // SetCloseHandler sets the handler for close messages received from the peer.
// The code argument to h is the received close code or CloseNoStatusReceived // The code argument to h is the received close code or CloseNoStatusReceived
// if the close message is empty. The default close handler sends a close // if the close message is empty. The default close handler sends a close frame
// message back to the peer. // back to the peer.
// //
// The application must read the connection to process close messages as // The application must read the connection to process close messages as
// described in the section on Control Messages above. // described in the section on Control Frames above.
// //
// The connection read methods return a CloseError when a close message is // The connection read methods return a CloseError when a close frame is
// received. Most applications should handle close messages as part of their // received. Most applications should handle close messages as part of their
// normal error handling. Applications should only set a close handler when the // normal error handling. Applications should only set a close handler when the
// application must perform some action before sending a close message back to // application must perform some action before sending a close frame back to
// the peer. // the peer.
func (c *Conn) SetCloseHandler(h func(code int, text string) error) { func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
if h == nil { if h == nil {
h = func(code int, text string) error { h = func(code int, text string) error {
message := FormatCloseMessage(code, "") message := []byte{}
if code != CloseNoStatusReceived {
message = FormatCloseMessage(code, "")
}
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
return nil return nil
} }
@@ -1076,11 +1077,11 @@ func (c *Conn) PingHandler() func(appData string) error {
} }
// SetPingHandler sets the handler for ping messages received from the peer. // SetPingHandler sets the handler for ping messages received from the peer.
// The appData argument to h is the PING message application data. The default // The appData argument to h is the PING frame application data. The default
// ping handler sends a pong to the peer. // ping handler sends a pong to the peer.
// //
// The application must read the connection to process ping messages as // The application must read the connection to process ping messages as
// described in the section on Control Messages above. // described in the section on Control Frames above.
func (c *Conn) SetPingHandler(h func(appData string) error) { func (c *Conn) SetPingHandler(h func(appData string) error) {
if h == nil { if h == nil {
h = func(message string) error { h = func(message string) error {
@@ -1102,11 +1103,11 @@ func (c *Conn) PongHandler() func(appData string) error {
} }
// SetPongHandler sets the handler for pong messages received from the peer. // SetPongHandler sets the handler for pong messages received from the peer.
// The appData argument to h is the PONG message application data. The default // The appData argument to h is the PONG frame application data. The default
// pong handler does nothing. // pong handler does nothing.
// //
// The application must read the connection to process ping messages as // The application must read the connection to process ping messages as
// described in the section on Control Messages above. // described in the section on Control Frames above.
func (c *Conn) SetPongHandler(h func(appData string) error) { func (c *Conn) SetPongHandler(h func(appData string) error) {
if h == nil { if h == nil {
h = func(string) error { return nil } h = func(string) error { return nil }
@@ -1140,14 +1141,7 @@ func (c *Conn) SetCompressionLevel(level int) error {
} }
// FormatCloseMessage formats closeCode and text as a WebSocket close message. // FormatCloseMessage formats closeCode and text as a WebSocket close message.
// An empty message is returned for code CloseNoStatusReceived.
func FormatCloseMessage(closeCode int, text string) []byte { func FormatCloseMessage(closeCode int, text string) []byte {
if closeCode == CloseNoStatusReceived {
// Return empty message because it's illegal to send
// CloseNoStatusReceived. Return non-nil value in case application
// checks for nil.
return []byte{}
}
buf := make([]byte, 2+len(text)) buf := make([]byte, 2+len(text))
binary.BigEndian.PutUint16(buf, uint16(closeCode)) binary.BigEndian.PutUint16(buf, uint16(closeCode))
copy(buf[2:], text) copy(buf[2:], text)

View File

@@ -341,6 +341,7 @@ func TestUnderlyingConn(t *testing.T) {
} }
func TestBufioReadBytes(t *testing.T) { func TestBufioReadBytes(t *testing.T) {
// Test calling bufio.ReadBytes for value longer than read buffer size. // Test calling bufio.ReadBytes for value longer than read buffer size.
m := make([]byte, 512) m := make([]byte, 512)
@@ -365,7 +366,7 @@ func TestBufioReadBytes(t *testing.T) {
t.Fatalf("ReadBytes() returned %v", err) t.Fatalf("ReadBytes() returned %v", err)
} }
if len(p) != len(m) { if len(p) != len(m) {
t.Fatalf("read returned %d bytes, want %d bytes", len(p), len(m)) t.Fatalf("read returnd %d bytes, want %d bytes", len(p), len(m))
} }
} }

View File

@@ -6,8 +6,9 @@
// //
// Overview // Overview
// //
// The Conn type represents a WebSocket connection. A server application calls // The Conn type represents a WebSocket connection. A server application uses
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: // the Upgrade function from an Upgrader object with a HTTP request handler
// to get a pointer to a Conn:
// //
// var upgrader = websocket.Upgrader{ // var upgrader = websocket.Upgrader{
// ReadBufferSize: 1024, // ReadBufferSize: 1024,
@@ -30,12 +31,10 @@
// for { // for {
// messageType, p, err := conn.ReadMessage() // messageType, p, err := conn.ReadMessage()
// if err != nil { // if err != nil {
// log.Println(err)
// return // return
// } // }
// if err := conn.WriteMessage(messageType, p); err != nil { // if err = conn.WriteMessage(messageType, p); err != nil {
// log.Println(err) // return err
// return
// } // }
// } // }
// //
@@ -86,26 +85,20 @@
// and pong. Call the connection WriteControl, WriteMessage or NextWriter // and pong. Call the connection WriteControl, WriteMessage or NextWriter
// methods to send a control message to the peer. // methods to send a control message to the peer.
// //
// Connections handle received close messages by calling the handler function // Connections handle received close messages by sending a close message to the
// set with the SetCloseHandler method and by returning a *CloseError from the // peer and returning a *CloseError from the the NextReader, ReadMessage or the
// NextReader, ReadMessage or the message Read method. The default close // message Read method.
// handler sends a close message to the peer.
// //
// Connections handle received ping messages by calling the handler function // Connections handle received ping and pong messages by invoking callback
// set with the SetPingHandler method. The default ping handler sends a pong // functions set with SetPingHandler and SetPongHandler methods. The callback
// message to the peer. // functions are called from the NextReader, ReadMessage and the message Read
// methods.
// //
// Connections handle received pong messages by calling the handler function // The default ping handler sends a pong to the peer. The application's reading
// set with the SetPongHandler method. The default pong handler does nothing. // goroutine can block for a short time while the handler writes the pong data
// If an application sends ping messages, then the application should set a // to the connection.
// pong handler to receive the corresponding pong.
// //
// The control message handler functions are called from the NextReader, // The application must read the connection to process ping, pong and close
// ReadMessage and message reader Read methods. The default close and ping
// handlers can block these methods for a short time when the handler writes to
// the connection.
//
// The application must read the connection to process close, ping and pong
// messages sent from the peer. If the application is not otherwise interested // messages sent from the peer. If the application is not otherwise interested
// in messages from the peer, then the application should start a goroutine to // in messages from the peer, then the application should start a goroutine to
// read and discard messages from the peer. A simple example is: // read and discard messages from the peer. A simple example is:
@@ -154,9 +147,9 @@
// CheckOrigin: func(r *http.Request) bool { return true }, // CheckOrigin: func(r *http.Request) bool { return true },
// } // }
// //
// The deprecated package-level Upgrade function does not perform origin // The deprecated Upgrade function does not enforce an origin policy. It's the
// checking. The application is responsible for checking the Origin header // application's responsibility to check the Origin header before calling
// before calling the Upgrade function. // Upgrade.
// //
// Compression EXPERIMENTAL // Compression EXPERIMENTAL
// //

View File

@@ -1,6 +1,6 @@
# Chat Example # Chat Example
This application shows how to use the This application shows how to use use the
[websocket](https://github.com/gorilla/websocket) package to implement a simple [websocket](https://github.com/gorilla/websocket) package to implement a simple
web chat application. web chat application.

View File

@@ -64,7 +64,7 @@ func (c *Client) readPump() {
for { for {
_, message, err := c.conn.ReadMessage() _, message, err := c.conn.ReadMessage()
if err != nil { if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
log.Printf("error: %v", err) log.Printf("error: %v", err)
} }
break break
@@ -113,7 +113,7 @@ func (c *Client) writePump() {
} }
case <-ticker.C: case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait)) c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return return
} }
} }

View File

@@ -55,7 +55,6 @@ func main() {
var homeTemplate = template.Must(template.New("").Parse(` var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html> <!DOCTYPE html>
<html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<script> <script>

View File

@@ -9,14 +9,12 @@ import (
"io" "io"
) )
// WriteJSON writes the JSON encoding of v as a message. // WriteJSON is deprecated, use c.WriteJSON instead.
//
// Deprecated: Use c.WriteJSON instead.
func WriteJSON(c *Conn, v interface{}) error { func WriteJSON(c *Conn, v interface{}) error {
return c.WriteJSON(v) return c.WriteJSON(v)
} }
// WriteJSON writes the JSON encoding of v as a message. // WriteJSON writes the JSON encoding of v to the connection.
// //
// See the documentation for encoding/json Marshal for details about the // See the documentation for encoding/json Marshal for details about the
// conversion of Go values to JSON. // conversion of Go values to JSON.
@@ -33,10 +31,7 @@ func (c *Conn) WriteJSON(v interface{}) error {
return err2 return err2
} }
// ReadJSON reads the next JSON-encoded message from the connection and stores // ReadJSON is deprecated, use c.ReadJSON instead.
// it in the value pointed to by v.
//
// Deprecated: Use c.ReadJSON instead.
func ReadJSON(c *Conn, v interface{}) error { func ReadJSON(c *Conn, v interface{}) error {
return c.ReadJSON(v) return c.ReadJSON(v)
} }

View File

@@ -11,6 +11,7 @@ import "unsafe"
const wordSize = int(unsafe.Sizeof(uintptr(0))) const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key [4]byte, pos int, b []byte) int { func maskBytes(key [4]byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers. // Mask one byte at a time for small buffers.
if len(b) < 2*wordSize { if len(b) < 2*wordSize {
for i := range b { for i := range b {

View File

@@ -1,77 +0,0 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"encoding/base64"
"errors"
"net"
"net/http"
"net/url"
"strings"
)
type netDialerFunc func(netowrk, addr string) (net.Conn, error)
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
return fn(network, addr)
}
func init() {
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
})
}
type httpProxyDialer struct {
proxyURL *url.URL
fowardDial func(network, addr string) (net.Conn, error)
}
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
hostPort, _ := hostPortNoPort(hpd.proxyURL)
conn, err := hpd.fowardDial(network, hostPort)
if err != nil {
return nil, err
}
connectHeader := make(http.Header)
if user := hpd.proxyURL.User; user != nil {
proxyUser := user.Username()
if proxyPassword, passwordSet := user.Password(); passwordSet {
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
}
}
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: connectHeader,
}
if err := connectReq.Write(conn); err != nil {
conn.Close()
return nil, err
}
// Read response. It's OK to use and discard buffered reader here becaue
// the remote server does not speak until spoken to.
br := bufio.NewReader(conn)
resp, err := http.ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
conn.Close()
f := strings.SplitN(resp.Status, " ", 2)
return nil, errors.New(f[1])
}
return conn, nil
}

View File

@@ -76,7 +76,7 @@ func checkSameOrigin(r *http.Request) bool {
if err != nil { if err != nil {
return false return false
} }
return equalASCIIFold(u.Host, r.Host) return u.Host == r.Host
} }
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
@@ -104,28 +104,26 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
// If the upgrade fails, then Upgrade replies to the client with an HTTP error // If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response. // response.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
const badHandshake = "websocket: the client is not using the websocket protocol: "
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
}
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
}
if r.Method != "GET" { if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
} }
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
} }
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
}
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
}
checkOrigin := u.CheckOrigin checkOrigin := u.CheckOrigin
if checkOrigin == nil { if checkOrigin == nil {
checkOrigin = checkSameOrigin checkOrigin = checkSameOrigin
@@ -232,11 +230,10 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
// Upgrade upgrades the HTTP server connection to the WebSocket protocol. // Upgrade upgrades the HTTP server connection to the WebSocket protocol.
// //
// Deprecated: Use websocket.Upgrader instead. // This function is deprecated, use websocket.Upgrader instead.
// //
// Upgrade does not perform origin checking. The application is responsible for // The application is responsible for checking the request origin before
// checking the Origin header before calling Upgrade. An example implementation // calling Upgrade. An example implementation of the same origin policy is:
// of the same origin policy check is:
// //
// if req.Header.Get("Origin") != "http://"+req.Host { // if req.Header.Get("Origin") != "http://"+req.Host {
// http.Error(w, "Origin not allowed", 403) // http.Error(w, "Origin not allowed", 403)

View File

@@ -49,21 +49,3 @@ func TestIsWebSocketUpgrade(t *testing.T) {
} }
} }
} }
var checkSameOriginTests = []struct {
ok bool
r *http.Request
}{
{false, &http.Request{Host: "example.org", Header: map[string][]string{"Origin": []string{"https://other.org"}}}},
{true, &http.Request{Host: "example.org", Header: map[string][]string{"Origin": []string{"https://example.org"}}}},
{true, &http.Request{Host: "Example.org", Header: map[string][]string{"Origin": []string{"https://example.org"}}}},
}
func TestCheckSameOrigin(t *testing.T) {
for _, tt := range checkSameOriginTests {
ok := checkSameOrigin(tt.r)
if tt.ok != ok {
t.Errorf("checkSameOrigin(%+v) returned %v, want %v", tt.r, ok, tt.ok)
}
}
}

View File

@@ -11,7 +11,6 @@ import (
"io" "io"
"net/http" "net/http"
"strings" "strings"
"unicode/utf8"
) )
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
@@ -112,14 +111,14 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
case escape: case escape:
escape = false escape = false
p[j] = b p[j] = b
j++ j += 1
case b == '\\': case b == '\\':
escape = true escape = true
case b == '"': case b == '"':
return string(p[:j]), s[i+1:] return string(p[:j]), s[i+1:]
default: default:
p[j] = b p[j] = b
j++ j += 1
} }
} }
return "", "" return "", ""
@@ -128,31 +127,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
return "", "" return "", ""
} }
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
func equalASCIIFold(s, t string) bool {
for s != "" && t != "" {
sr, size := utf8.DecodeRuneInString(s)
s = s[size:]
tr, size := utf8.DecodeRuneInString(t)
t = t[size:]
if sr == tr {
continue
}
if 'A' <= sr && sr <= 'Z' {
sr = sr + 'a' - 'A'
}
if 'A' <= tr && tr <= 'Z' {
tr = tr + 'a' - 'A'
}
if sr != tr {
return false
}
}
return s == t
}
// tokenListContainsValue returns true if the 1#token header with the given // tokenListContainsValue returns true if the 1#token header with the given
// name contains a token equal to value with ASCII case folding. // name contains token.
func tokenListContainsValue(header http.Header, name string, value string) bool { func tokenListContainsValue(header http.Header, name string, value string) bool {
headers: headers:
for _, s := range header[name] { for _, s := range header[name] {
@@ -166,7 +142,7 @@ headers:
if s != "" && s[0] != ',' { if s != "" && s[0] != ',' {
continue headers continue headers
} }
if equalASCIIFold(t, value) { if strings.EqualFold(t, value) {
return true return true
} }
if s == "" { if s == "" {
@@ -180,6 +156,7 @@ headers:
// parseExtensiosn parses WebSocket extensions from a header. // parseExtensiosn parses WebSocket extensions from a header.
func parseExtensions(header http.Header) []map[string]string { func parseExtensions(header http.Header) []map[string]string {
// From RFC 6455: // From RFC 6455:
// //
// Sec-WebSocket-Extensions = extension-list // Sec-WebSocket-Extensions = extension-list

View File

@@ -10,24 +10,6 @@ import (
"testing" "testing"
) )
var equalASCIIFoldTests = []struct {
t, s string
eq bool
}{
{"WebSocket", "websocket", true},
{"websocket", "WebSocket", true},
{"Öyster", "öyster", false},
}
func TestEqualASCIIFold(t *testing.T) {
for _, tt := range equalASCIIFoldTests {
eq := equalASCIIFold(tt.s, tt.t)
if eq != tt.eq {
t.Errorf("equalASCIIFold(%q, %q) = %v, want %v", tt.s, tt.t, eq, tt.eq)
}
}
}
var tokenListContainsValueTests = []struct { var tokenListContainsValueTests = []struct {
value string value string
ok bool ok bool
@@ -56,32 +38,29 @@ var parseExtensionTests = []struct {
value string value string
extensions []map[string]string extensions []map[string]string
}{ }{
{`foo`, []map[string]string{{"": "foo"}}}, {`foo`, []map[string]string{map[string]string{"": "foo"}}},
{`foo, bar; baz=2`, []map[string]string{ {`foo, bar; baz=2`, []map[string]string{
{"": "foo"}, map[string]string{"": "foo"},
{"": "bar", "baz": "2"}}}, map[string]string{"": "bar", "baz": "2"}}},
{`foo; bar="b,a;z"`, []map[string]string{ {`foo; bar="b,a;z"`, []map[string]string{
{"": "foo", "bar": "b,a;z"}}}, map[string]string{"": "foo", "bar": "b,a;z"}}},
{`foo , bar; baz = 2`, []map[string]string{ {`foo , bar; baz = 2`, []map[string]string{
{"": "foo"}, map[string]string{"": "foo"},
{"": "bar", "baz": "2"}}}, map[string]string{"": "bar", "baz": "2"}}},
{`foo, bar; baz=2 junk`, []map[string]string{ {`foo, bar; baz=2 junk`, []map[string]string{
{"": "foo"}}}, map[string]string{"": "foo"}}},
{`foo junk, bar; baz=2 junk`, nil}, {`foo junk, bar; baz=2 junk`, nil},
{`mux; max-channels=4; flow-control, deflate-stream`, []map[string]string{ {`mux; max-channels=4; flow-control, deflate-stream`, []map[string]string{
{"": "mux", "max-channels": "4", "flow-control": ""}, map[string]string{"": "mux", "max-channels": "4", "flow-control": ""},
{"": "deflate-stream"}}}, map[string]string{"": "deflate-stream"}}},
{`permessage-foo; x="10"`, []map[string]string{ {`permessage-foo; x="10"`, []map[string]string{
{"": "permessage-foo", "x": "10"}}}, map[string]string{"": "permessage-foo", "x": "10"}}},
{`permessage-foo; use_y, permessage-foo`, []map[string]string{ {`permessage-foo; use_y, permessage-foo`, []map[string]string{
{"": "permessage-foo", "use_y": ""}, map[string]string{"": "permessage-foo", "use_y": ""},
{"": "permessage-foo"}}}, map[string]string{"": "permessage-foo"}}},
{`permessage-deflate; client_max_window_bits; server_max_window_bits=10 , permessage-deflate; client_max_window_bits`, []map[string]string{ {`permessage-deflate; client_max_window_bits; server_max_window_bits=10 , permessage-deflate; client_max_window_bits`, []map[string]string{
{"": "permessage-deflate", "client_max_window_bits": "", "server_max_window_bits": "10"}, map[string]string{"": "permessage-deflate", "client_max_window_bits": "", "server_max_window_bits": "10"},
{"": "permessage-deflate", "client_max_window_bits": ""}}}, map[string]string{"": "permessage-deflate", "client_max_window_bits": ""}}},
{"permessage-deflate; server_no_context_takeover; client_max_window_bits=15", []map[string]string{
{"": "permessage-deflate", "server_no_context_takeover": "", "client_max_window_bits": "15"},
}},
} }
func TestParseExtensions(t *testing.T) { func TestParseExtensions(t *testing.T) {

View File

@@ -1,473 +0,0 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
// Package proxy provides support for a variety of protocols to proxy network
// data.
//
package websocket
import (
"errors"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"sync"
)
type proxy_direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var proxy_Direct = proxy_direct{}
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
// A PerHost directs connections to a default Dialer unless the host name
// requested matches one of a number of exceptions.
type proxy_PerHost struct {
def, bypass proxy_Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
return &proxy_PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone ".example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a host name
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *proxy_PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *proxy_PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *proxy_PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a host name that will use the bypass proxy.
func (p *proxy_PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}
// A Dialer is a means to establish a connection.
type proxy_Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type proxy_Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func proxy_FromEnvironment() proxy_Dialer {
allProxy := proxy_allProxyEnv.Get()
if len(allProxy) == 0 {
return proxy_Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return proxy_Direct
}
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
if err != nil {
return proxy_Direct
}
noProxy := proxy_noProxyEnv.Get()
if len(noProxy) == 0 {
return proxy
}
perHost := proxy_NewPerHost(proxy, proxy_Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
if proxy_proxySchemes == nil {
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
}
proxy_proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
var auth *proxy_Auth
if u.User != nil {
auth = new(proxy_Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return proxy_SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxy_proxySchemes != nil {
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}
var (
proxy_allProxyEnv = &proxy_envOnce{
names: []string{"ALL_PROXY", "all_proxy"},
}
proxy_noProxyEnv = &proxy_envOnce{
names: []string{"NO_PROXY", "no_proxy"},
}
)
// envOnce looks up an environment variable (optionally by multiple
// names) once. It mitigates expensive lookups on some platforms
// (e.g. Windows).
// (Borrowed from net/http/transport.go)
type proxy_envOnce struct {
names []string
once sync.Once
val string
}
func (e *proxy_envOnce) Get() string {
e.once.Do(e.init)
return e.val
}
func (e *proxy_envOnce) init() {
for _, n := range e.names {
e.val = os.Getenv(n)
if e.val != "" {
return
}
}
}
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928 and RFC 1929.
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
s := &proxy_socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type proxy_socks5 struct {
user, password string
network, addr string
forward proxy_Dialer
}
const proxy_socks5Version = 5
const (
proxy_socks5AuthNone = 0
proxy_socks5AuthPassword = 2
)
const proxy_socks5Connect = 1
const (
proxy_socks5IP4 = 1
proxy_socks5Domain = 3
proxy_socks5IP6 = 4
)
var proxy_socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
if err := s.connect(conn, addr); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target,
// which must be a canonical address with a host and port.
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
host, portStr, err := net.SplitHostPort(target)
if err != nil {
return err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, proxy_socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
// See RFC 1929
if buf[1] == proxy_socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, proxy_socks5IP4)
ip = ip4
} else {
buf = append(buf, proxy_socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination host name too long: " + host)
}
buf = append(buf, proxy_socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(proxy_socks5Errors) {
failure = proxy_socks5Errors[buf[1]]
}
if len(failure) > 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case proxy_socks5IP4:
bytesToDiscard = net.IPv4len
case proxy_socks5IP6:
bytesToDiscard = net.IPv6len
case proxy_socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
return nil
}

View File

@@ -21,4 +21,3 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof

362
vendor/github.com/hashicorp/yamux/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

86
vendor/github.com/hashicorp/yamux/README.md generated vendored Normal file
View File

@@ -0,0 +1,86 @@
# Yamux
Yamux (Yet another Multiplexer) is a multiplexing library for Golang.
It relies on an underlying connection to provide reliability
and ordering, such as TCP or Unix domain sockets, and provides
stream-oriented multiplexing. It is inspired by SPDY but is not
interoperable with it.
Yamux features include:
* Bi-directional streams
* Streams can be opened by either client or server
* Useful for NAT traversal
* Server-side push support
* Flow control
* Avoid starvation
* Back-pressure to prevent overwhelming a receiver
* Keep Alives
* Enables persistent connections over a load balancer
* Efficient
* Enables thousands of logical streams with low overhead
## Documentation
For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/yamux).
## Specification
The full specification for Yamux is provided in the `spec.md` file.
It can be used as a guide to implementors of interoperable libraries.
## Usage
Using Yamux is remarkably simple:
```go
func client() {
// Get a TCP connection
conn, err := net.Dial(...)
if err != nil {
panic(err)
}
// Setup client side of yamux
session, err := yamux.Client(conn, nil)
if err != nil {
panic(err)
}
// Open a new stream
stream, err := session.Open()
if err != nil {
panic(err)
}
// Stream implements net.Conn
stream.Write([]byte("ping"))
}
func server() {
// Accept a TCP connection
conn, err := listener.Accept()
if err != nil {
panic(err)
}
// Setup server side of yamux
session, err := yamux.Server(conn, nil)
if err != nil {
panic(err)
}
// Accept a stream
stream, err := session.Accept()
if err != nil {
panic(err)
}
// Listen for a message
buf := make([]byte, 4)
stream.Read(buf)
}
```

60
vendor/github.com/hashicorp/yamux/addr.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package yamux
import (
"fmt"
"net"
)
// hasAddr is used to get the address from the underlying connection
type hasAddr interface {
LocalAddr() net.Addr
RemoteAddr() net.Addr
}
// yamuxAddr is used when we cannot get the underlying address
type yamuxAddr struct {
Addr string
}
func (*yamuxAddr) Network() string {
return "yamux"
}
func (y *yamuxAddr) String() string {
return fmt.Sprintf("yamux:%s", y.Addr)
}
// Addr is used to get the address of the listener.
func (s *Session) Addr() net.Addr {
return s.LocalAddr()
}
// LocalAddr is used to get the local address of the
// underlying connection.
func (s *Session) LocalAddr() net.Addr {
addr, ok := s.conn.(hasAddr)
if !ok {
return &yamuxAddr{"local"}
}
return addr.LocalAddr()
}
// RemoteAddr is used to get the address of remote end
// of the underlying connection
func (s *Session) RemoteAddr() net.Addr {
addr, ok := s.conn.(hasAddr)
if !ok {
return &yamuxAddr{"remote"}
}
return addr.RemoteAddr()
}
// LocalAddr returns the local address
func (s *Stream) LocalAddr() net.Addr {
return s.session.LocalAddr()
}
// LocalAddr returns the remote address
func (s *Stream) RemoteAddr() net.Addr {
return s.session.RemoteAddr()
}

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