Browse Source

Merge pull request #403 from fatedier/dev

bump version to v0.13.0
pull/461/head v0.13.0
fatedier 7 years ago committed by GitHub
parent
commit
066172e9c1
  1. 4
      Dockerfile
  2. 8
      Godeps/Godeps.json
  3. 108
      README.md
  4. 124
      README_zh.md
  5. 60
      client/admin.go
  6. 78
      client/admin_api.go
  7. 273
      client/control.go
  8. 35
      client/proxy.go
  9. 21
      client/service.go
  10. 145
      client/vistor.go
  11. 71
      cmd/frpc/main.go
  12. 31
      conf/frpc_full.ini
  13. 5
      conf/frps_full.ini
  14. 36
      models/config/client_common.go
  15. 233
      models/config/proxy.go
  16. 17
      models/config/server_common.go
  17. 1
      models/consts/consts.go
  18. 42
      models/msg/msg.go
  19. 65
      models/plugin/socks5.go
  20. 1
      server/control.go
  21. 114
      server/dashboard.go
  22. 74
      server/manager.go
  23. 36
      server/proxy.go
  24. 38
      server/service.go
  25. 5
      utils/log/log.go
  26. 105
      utils/net/http.go
  27. 53
      utils/net/listener.go
  28. 2
      utils/version/version.go
  29. 51
      utils/vhost/http.go
  30. 3
      utils/vhost/resource.go
  31. 5
      utils/vhost/vhost.go
  32. 22
      vendor/github.com/armon/go-socks5/.gitignore
  33. 4
      vendor/github.com/armon/go-socks5/.travis.yml
  34. 20
      vendor/github.com/armon/go-socks5/LICENSE
  35. 45
      vendor/github.com/armon/go-socks5/README.md
  36. 151
      vendor/github.com/armon/go-socks5/auth.go
  37. 17
      vendor/github.com/armon/go-socks5/credentials.go
  38. 364
      vendor/github.com/armon/go-socks5/request.go
  39. 23
      vendor/github.com/armon/go-socks5/resolver.go
  40. 41
      vendor/github.com/armon/go-socks5/ruleset.go
  41. 169
      vendor/github.com/armon/go-socks5/socks5.go
  42. 156
      vendor/golang.org/x/net/context/context.go
  43. 72
      vendor/golang.org/x/net/context/go17.go
  44. 300
      vendor/golang.org/x/net/context/pre_go17.go

4
Dockerfile

@ -6,8 +6,8 @@ RUN cd /go/src/github.com/fatedier/frp \
&& make \
&& mv bin/frpc /frpc \
&& mv bin/frps /frps \
&& mv conf/frpc_min.ini /frpc.ini \
&& mv conf/frps_min.ini /frps.ini \
&& mv conf/frpc.ini /frpc.ini \
&& mv conf/frps.ini /frps.ini \
&& make clean
WORKDIR /

8
Godeps/Godeps.json generated vendored

@ -6,6 +6,10 @@
"./..."
],
"Deps": [
{
"ImportPath": "github.com/armon/go-socks5",
"Rev": "e75332964ef517daa070d7c38a9466a0d687e0a5"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
@ -110,6 +114,10 @@
"ImportPath": "golang.org/x/net/bpf",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"

108
README.md

@ -19,18 +19,21 @@ 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)
* [Forward DNS query request](#forward-dns-query-request)
* [Forward unix domain socket](#forward-unix-domain-socket)
* [Expose your service in security](#expose-your-service-in-security)
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
* [Features](#features)
* [Configuration File](#configuration-file)
* [Dashboard](#dashboard)
* [Authentication](#authentication)
* [Encryption and Compression](#encryption-and-compression)
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
* [Privilege Mode](#privilege-mode)
* [Port White List](#port-white-list)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Support KCP Protocol](#support-kcp-protocol)
* [Connection Pool](#connection-pool)
* [Rewriting the Host Header](#rewriting-the-host-header)
* [Get Real IP](#get-real-ip)
* [Password protecting your web service](#password-protecting-your-web-service)
* [Custom subdomain names](#custom-subdomain-names)
* [URL routing](#url-routing)
@ -187,19 +190,36 @@ However, we can expose a http or https service using frp.
Using tcp port to connect unix domain socket like docker daemon.
1. Modify frps.ini:
Configure frps same as above.
1. Start frpc with configurations:
```ini
# frps.ini
# frpc.ini
[common]
bind_port = 7000
server_addr = x.x.x.x
server_port = 7000
[unix_domain_socket]
type = tcp
remote_port = 6000
plugin = unix_domain_socket
plugin_unix_path = /var/run/docker.sock
```
2. Start frps:
2. Get docker version by curl command:
`./frps -c ./frps.ini`
`curl http://x.x.x.x:6000/version`
### Expose your service in security
For some services, if expose them to the public network directly will be a security risk.
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
Configure frps same as above.
3. Modify frpc.ini:
1. Start frpc, forward ssh port and `remote_port` is useless:
```ini
# frpc.ini
@ -207,26 +227,39 @@ Using tcp port to connect unix domain socket like docker daemon.
server_addr = x.x.x.x
server_port = 7000
[unix_domain_socket]
type = tcp
remote_port = 6000
plugin = unix_domain_socket
plugin_unix_path = /var/run/docker.sock
[secret_ssh]
type = stcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
4. Start frpc:
2. Start another frpc in which you want to connect this ssh server:
`./frpc -c ./frpc.ini`
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
type = stcp
role = vistor
server_name = secret_ssh
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 6000
```
5. Get docker version by curl command:
3. Connect to server in LAN by ssh assuming that username is test:
`curl http://x.x.x.x:6000/version`
`ssh -oPort=6000 test@127.0.0.1`
### Connect website through frpc's network
Configure frps same as above.
1. Modify frpc.ini:
1. Start frpc with configurations:
```ini
# frpc.ini
@ -237,16 +270,20 @@ Configure frps same as above.
[http_proxy]
type = tcp
remote_port = 6000
plugin = http_proxy
plugin = http_proxy # or socks5
```
4. Start frpc:
2. Set http proxy or socks5 proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
`./frpc -c ./frpc.ini`
## Features
5. Set http proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
### Configuration File
## Features
You can find features which this document not metioned from full example configuration files.
[frps full configuration file](./conf/frps_full.ini)
[frpc full configuration file](./conf/frpc_full.ini)
### Dashboard
@ -288,9 +325,20 @@ use_encryption = true
use_compression = true
```
### Reload configures without frps stopped
### Hot-Reload frpc configuration
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
```ini
# frpc.ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
```
Then run command `frpc -c ./frpc.ini --reload` and wait for about 10 seconds to let frpc create or update or delete proxies.
This feature is removed since v0.10.0.
**Note that parameters in [common] section won't be modified except 'start' now.**
### Privilege Mode
@ -386,6 +434,14 @@ host_header_rewrite = dev.yourdomain.com
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
### Get Real IP
Features for http proxy only.
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
**Note that now you can only get these two headers in first request of each user connection.**
### Password protecting your web service
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
@ -469,7 +525,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
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** 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** 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.
@ -505,7 +561,7 @@ Interested in getting involved? We would like to help you!
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
* If you have some wanderful ideas, send email to fatedier@gmail.com.
* If you have some wonderful ideas, send email to fatedier@gmail.com.
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**

124
README_zh.md

@ -17,18 +17,21 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
* [转发 DNS 查询请求](#转发-dns-查询请求)
* [转发 Unix域套接字](#转发-unix域套接字)
* [安全地暴露内网服务](#安全地暴露内网服务)
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
* [功能说明](#功能说明)
* [配置文件](#配置文件)
* [Dashboard](#dashboard)
* [身份验证](#身份验证)
* [加密与压缩](#加密与压缩)
* [服务器端热加载配置文件](#服务器端热加载配置文件)
* [客户端热加载配置文件](#客户端热加载配置文件)
* [特权模式](#特权模式)
* [端口白名单](#端口白名单)
* [TCP 多路复用](#tcp-多路复用)
* [支持 kcp 协议](#支持-kcp-协议)
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
* [连接池](#连接池)
* [修改 Host Header](#修改-host-header)
* [获取用户真实 IP](#获取用户真实-ip)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由)
@ -188,19 +191,9 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
1. 修改 frps.ini 文件:
```ini
# frps.ini
[common]
bind_port = 7000
```
2. 启动 frps:
`./frps -c ./frps.ini`
frps 的部署步骤同上。
3. 修改 frpc.ini 文件,启用 unix_domain_socket 插件
1. 启动 frpc,启用 unix_domain_socket 插件,配置如下:
```ini
# frpc.ini
@ -215,21 +208,67 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
plugin_unix_path = /var/run/docker.sock
```
4. 启动 frpc:
2. 通过 curl 命令查看 docker 版本信息
`./frpc -c ./frpc.ini`
`curl http://x.x.x.x:6000/version`
5. 通过 curl 命令查看 docker 版本信息
### 安全地暴露内网服务
`curl http://x.x.x.x:6000/version`
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
使用 **stcp(secret tcp)** 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc。
以下示例将会创建一个只有自己能访问到的 ssh 服务代理。
frps 的部署步骤同上。
1. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[secret_ssh]
type = stcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
```
2. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
[secret_ssh_vistor]
type = stcp
# stcp 的访问者
role = vistor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
sk = abcdefg
# 绑定本地端口用于访问 ssh 服务
bind_addr = 127.0.0.1
bind_port = 6000
```
3. 通过 ssh 访问内网机器,假设用户名为 test:
`ssh -oPort=6000 test@127.0.0.1`
### 通过 frpc 所在机器访问外网
frpc 内置了 http proxy 插件,可以使其他机器通过 frpc 的网络访问互联网。
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
frps 的部署步骤同上。
1. 修改 frpc.ini 文件,启用 http_proxy 插件:
1. 启动 frpc,启用 http_proxy 或 socks5 插件(plugin 换为 socks5 即可), 配置如下
```ini
# frpc.ini
@ -243,13 +282,17 @@ frps 的部署步骤同上。
plugin = http_proxy
```
4. 启动 frpc:
2. 浏览器设置 http 或 socks5 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
`./frpc -c ./frpc.ini`
## 功能说明
5. 浏览器设置 http 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
### 配置文件
## 功能说明
由于 frp 目前支持的功能和配置项较多,未在文档中列出的功能可以从完整的示例配置文件中发现。
[frps 完整配置文件](./conf/frps_full.ini)
[frpc 完整配置文件](./conf/frpc_full.ini)
### Dashboard
@ -295,9 +338,26 @@ use_compression = true
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
### 服务器端热加载配置文件
### 客户端热加载配置文件
当修改了 frpc 中的代理配置,可以通过 `frpc --reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,这个功能暂时移除。
```ini
# frpc.ini
[common]
admin_addr = 127.0.0.1
admin_port = 7400
```
之后执行重启命令:
`frpc -c ./frpc.ini --reload`
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
### 特权模式
@ -327,7 +387,7 @@ privilege_allow_ports 可以配置允许使用的某个指定端口或者是一
tcp_mux = false
```
### 支持 kcp 协议
### 底层通信可选 kcp 协议
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
@ -393,6 +453,12 @@ host_header_rewrite = dev.yourdomain.com
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`
### 获取用户真实 IP
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For``X-Real-IP` 来获取用户真实 IP。
**需要注意的是,目前只在每一个用户连接的第一个 HTTP 请求中添加了这两个 header。**
### 通过密码保护你的 web 服务
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
@ -486,9 +552,9 @@ http_proxy = http://user:pwd@192.168.1.128:8080
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**。具体使用方式请查看[使用示例](#使用示例)。
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port 不再需要配置。
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip``local_port` 不再需要配置。
使用 **http_proxy** 插件的示例:

60
client/admin.go

@ -0,0 +1,60 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"net"
"net/http"
"time"
"github.com/fatedier/frp/models/config"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
)
var (
httpServerReadTimeout = 10 * time.Second
httpServerWriteTimeout = 10 * time.Second
)
func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
// url router
router := httprouter.New()
user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd
// api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout,
}
if address == "" {
address = ":http"
}
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
go server.Serve(ln)
return
}

78
client/admin_api.go

@ -0,0 +1,78 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"encoding/json"
"net/http"
"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
// api/reload
type ReloadResp struct {
GeneralResponse
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res ReloadResp
)
defer func() {
log.Info("Http response [/api/reload]: code [%d]", res.Code)
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/reload]")
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
}
newCommonCfg, err := config.LoadClientCommonConf(conf)
if err != nil {
res.Code = 2
res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err)
return
}
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
if err != nil {
res.Code = 3
res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err)
return
}
svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
log.Info("success reload conf")
return
}

273
client/control.go

@ -24,8 +24,9 @@ import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/net"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/xtaci/smux"
@ -48,8 +49,14 @@ type Control struct {
// proxies
proxies map[string]Proxy
// vistor configures
vistorCfgs map[string]config.ProxyConf
// vistors
vistors map[string]Vistor
// control connection
conn net.Conn
conn frpNet.Conn
// tcp stream multiplexing, if enabled
session *smux.Session
@ -63,8 +70,8 @@ type Control struct {
// run id got from server
runId string
// connection or other error happens , control will try to reconnect to server
closed int32
// if we call close() in control, do not reconnect to server
exit bool
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
closedCh chan int
@ -77,7 +84,7 @@ type Control struct {
log.Logger
}
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
@ -86,14 +93,16 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
proxies: make(map[string]Proxy),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
vistorCfgs: vistorCfgs,
proxies: make(map[string]Proxy),
vistors: make(map[string]Vistor),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
}
}
@ -105,16 +114,17 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
// 6. In controler(): ini readCh, sendCh, closedCh
// 7. In controler(): start new reader(), writer(), manager()
// controler() will keep running
func (ctl *Control) Run() error {
func (ctl *Control) Run() (err error) {
for {
err := ctl.login()
err = ctl.login()
if err != nil {
ctl.Warn("login to server failed: %v", err)
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and continues relogin to server
if config.ClientCommonCfg.LoginFailExit {
return err
return
} else {
ctl.Warn("login to server fail: %v", err)
time.Sleep(30 * time.Second)
}
} else {
@ -127,6 +137,18 @@ func (ctl *Control) Run() error {
go ctl.writer()
go ctl.reader()
// start all local vistors
for _, cfg := range ctl.vistorCfgs {
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
@ -137,29 +159,13 @@ func (ctl *Control) Run() error {
}
func (ctl *Control) NewWorkConn() {
var (
workConn net.Conn
err error
)
if config.ClientCommonCfg.TcpMux {
stream, err := ctl.session.OpenStream()
if err != nil {
ctl.Warn("start new work connection error: %v", err)
return
}
workConn = net.WrapConn(stream)
} else {
workConn, err = net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
ctl.Warn("start new work connection error: %v", err)
return
}
workConn, err := ctl.connectServer()
if err != nil {
return
}
m := &msg.NewWorkConn{
RunId: ctl.runId,
RunId: ctl.getRunId(),
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
@ -176,7 +182,8 @@ func (ctl *Control) NewWorkConn() {
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
pxy, ok := ctl.getProxy(startMsg.ProxyName)
if ok {
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
} else {
@ -184,6 +191,20 @@ func (ctl *Control) NewWorkConn() {
}
}
func (ctl *Control) Close() error {
ctl.mu.Lock()
ctl.exit = true
err := errors.PanicToError(func() {
for name, _ := range ctl.proxies {
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
})
ctl.mu.Unlock()
return err
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
@ -199,7 +220,7 @@ func (ctl *Control) login() (err error) {
ctl.session.Close()
}
conn, err := net.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
return err
@ -221,14 +242,14 @@ func (ctl *Control) login() (err error) {
session.Close()
return errRet
}
conn = net.WrapConn(stream)
conn = frpNet.WrapConn(stream)
ctl.session = session
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.runId
ctl.loginMsg.RunId = ctl.getRunId()
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
@ -249,7 +270,7 @@ func (ctl *Control) login() (err error) {
ctl.conn = conn
// update runId got from server
ctl.runId = loginRespMsg.RunId
ctl.setRunId(loginRespMsg.RunId)
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
@ -261,6 +282,27 @@ func (ctl *Control) login() (err error) {
return nil
}
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
if config.ClientCommonCfg.TcpMux {
stream, errRet := ctl.session.OpenStream()
if errRet != nil {
err = errRet
ctl.Warn("start new connection to server error: %v", err)
return
}
conn = frpNet.WrapConn(stream)
} else {
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
ctl.Warn("start new connection to server error: %v", err)
return
}
}
return
}
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
@ -305,6 +347,7 @@ func (ctl *Control) writer() {
}
}
// manager handles all channel events and do corresponding process
func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
@ -345,13 +388,14 @@ func (ctl *Control) manager() {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
cfg, ok := ctl.pxyCfgs[m.ProxyName]
cfg, ok := ctl.getProxyConf(m.ProxyName)
if !ok {
// it will never go to this branch now
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
continue
}
oldPxy, ok := ctl.proxies[m.ProxyName]
oldPxy, ok := ctl.getProxy(m.ProxyName)
if ok {
oldPxy.Close()
}
@ -363,7 +407,7 @@ func (ctl *Control) manager() {
}
continue
}
ctl.proxies[m.ProxyName] = pxy
ctl.addProxy(m.ProxyName, pxy)
ctl.Info("[%s] start proxy success", m.ProxyName)
case *msg.Pong:
ctl.lastPong = time.Now()
@ -373,26 +417,43 @@ func (ctl *Control) manager() {
}
}
// control keep watching closedCh, start a new connection if previous control connection is closed
// controler keep watching closedCh, start a new connection if previous control connection is closed.
// If controler is notified by closedCh, reader and writer and manager will exit, then recall these functions.
func (ctl *Control) controler() {
var err error
maxDelayTime := 30 * time.Second
delayTime := time.Second
checkInterval := 30 * time.Second
checkInterval := 10 * time.Second
checkProxyTicker := time.NewTicker(checkInterval)
for {
select {
case <-checkProxyTicker.C:
// Every 30 seconds, check which proxy registered failed and reregister it to server.
// Every 10 seconds, check which proxy registered failed and reregister it to server.
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
ctl.Info("try to register proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
for _, cfg := range ctl.vistorCfgs {
if _, exist := ctl.vistors[cfg.GetName()]; !exist {
ctl.Info("try to start vistor [%s]", cfg.GetName())
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
}
ctl.mu.RUnlock()
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
@ -403,6 +464,14 @@ func (ctl *Control) controler() {
for _, pxy := range ctl.proxies {
pxy.Close()
}
// if ctl.exit is true, just exit
ctl.mu.RLock()
exit := ctl.exit
ctl.mu.RUnlock()
if exit {
return
}
time.Sleep(time.Second)
// loop util reconnect to server success
@ -432,11 +501,13 @@ func (ctl *Control) controler() {
go ctl.reader()
// send NewProxy message for all configured proxies
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
ctl.mu.RUnlock()
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
@ -444,3 +515,107 @@ func (ctl *Control) controler() {
}
}
}
func (ctl *Control) setRunId(runId string) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.runId = runId
}
func (ctl *Control) getRunId() string {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
return ctl.runId
}
func (ctl *Control) getProxy(name string) (pxy Proxy, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
pxy, ok = ctl.proxies[name]
return
}
func (ctl *Control) addProxy(name string, pxy Proxy) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
ctl.proxies[name] = pxy
}
func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
conf, ok = ctl.pxyCfgs[name]
return
}
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
ctl.mu.Lock()
defer ctl.mu.Unlock()
removedPxyNames := make([]string, 0)
for name, oldCfg := range ctl.pxyCfgs {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}
if del {
removedPxyNames = append(removedPxyNames, name)
delete(ctl.pxyCfgs, name)
if pxy, ok := ctl.proxies[name]; ok {
pxy.Close()
}
delete(ctl.proxies, name)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
}
ctl.Info("proxy removed: %v", removedPxyNames)
addedPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := ctl.pxyCfgs[name]; !ok {
ctl.pxyCfgs[name] = cfg
addedPxyNames = append(addedPxyNames, name)
}
}
ctl.Info("proxy added: %v", addedPxyNames)
removedVistorName := make([]string, 0)
for name, oldVistorCfg := range ctl.vistorCfgs {
del := false
cfg, ok := vistorCfgs[name]
if !ok {
del = true
} else {
if !oldVistorCfg.Compare(cfg) {
del = true
}
}
if del {
removedVistorName = append(removedVistorName, name)
delete(ctl.vistorCfgs, name)
if vistor, ok := ctl.vistors[name]; ok {
vistor.Close()
}
delete(ctl.vistors, name)
}
}
ctl.Info("vistor removed: %v", removedVistorName)
addedVistorName := make([]string, 0)
for name, vistorCfg := range vistorCfgs {
if _, ok := ctl.vistorCfgs[name]; !ok {
ctl.vistorCfgs[name] = vistorCfg
addedVistorName = append(addedVistorName, name)
}
}
ctl.Info("vistor added: %v", addedVistorName)
}

35
client/proxy.go

@ -31,7 +31,7 @@ import (
frpNet "github.com/fatedier/frp/utils/net"
)
// Proxy defines how to work for different proxy type.
// Proxy defines how to deal with work connections for different proxy type.
type Proxy interface {
Run() error
@ -67,6 +67,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
}
return
}
@ -162,6 +167,34 @@ func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// STCP
type StcpProxy struct {
BaseProxy
cfg *config.StcpProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *StcpProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
return
}
}
return
}
func (pxy *StcpProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
}
// UDP
type UdpProxy struct {
BaseProxy

21
client/service.go

@ -14,7 +14,10 @@
package client
import "github.com/fatedier/frp/models/config"
import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)
type Service struct {
// manager control connection with server
@ -23,11 +26,11 @@ type Service struct {
closedCh chan int
}
func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
func NewService(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) (svr *Service) {
svr = &Service{
closedCh: make(chan int),
}
ctl := NewControl(svr, pxyCfgs)
ctl := NewControl(svr, pxyCfgs, vistorCfgs)
svr.ctl = ctl
return
}
@ -38,6 +41,18 @@ func (svr *Service) Run() error {
return err
}
if config.ClientCommonCfg.AdminPort != 0 {
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
if err != nil {
log.Warn("run admin server error: %v", err)
}
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
}
<-svr.closedCh
return nil
}
func (svr *Service) Close() error {
return svr.ctl.Close()
}

145
client/vistor.go

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

71
cmd/frpc/main.go

@ -15,10 +15,17 @@
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
@ -37,6 +44,7 @@ var usage string = `frpc is the client of frp
Usage:
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
frpc [-c config_file] --reload
frpc -h | --help
frpc -v | --version
@ -45,13 +53,14 @@ Options:
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
--reload reload configure file without program exit
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frpc.ini"
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)
@ -70,6 +79,47 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg.ConfigFile = confFile
// check if reload command
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
config.ClientCommonCfg.AdminPwd))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
res := &client.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
@ -106,7 +156,7 @@ func main() {
}
}
pxyCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
if err != nil {
fmt.Println(err)
os.Exit(1)
@ -115,10 +165,25 @@ func main() {
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
svr := client.NewService(pxyCfgs)
svr := client.NewService(pxyCfgs, vistorCfgs)
// 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)
}

31
conf/frpc_full.ini

@ -20,6 +20,12 @@ log_max_days = 3
# for authentication
privilege_token = 12345678
# set admin address for control frpc's action by http api such as reload
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin
# connections will be established in advance, default value is zero
pool_count = 5
@ -110,3 +116,28 @@ remote_port = 6004
plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
[secret_tcp]
# If the type is secret tcp, remote_port is useless
# Who want to connect local port should deploy another frpc with stcp proxy and role is vistor
type = stcp
# sk used for authentication for vistors
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
# user of frpc should be same in both stcp server and stcp vistor
[secret_tcp_vistor]
# frpc role vistor -> frps -> frpc role server
role = vistor
type = stcp
# the server name you want to vistor
server_name = secret_tcp
sk = abcdefg
# connect this address to vistor stcp server
bind_addr = 127.0.0.1
bind_port = 9000
use_encryption = false
use_compression = false

5
conf/frps_full.ini

@ -9,11 +9,14 @@ bind_port = 7000
# if not set, kcp is disabled in frps
kcp_bind_port = 7000
# specify which address proxy will listen for, default value is same with bind_addr
# proxy_bind_addr = 127.0.0.1
# if you want to support virtual host, you must set the http port for listening (optional)
vhost_http_port = 80
vhost_https_port = 443
# if you want to configure or reload frps by dashboard, dashboard_port must be set
# set dashboard_port to view dashboard of frps
dashboard_port = 7500
# dashboard user and pwd for basic auth protect, if not set, both default value is admin

36
models/config/client_common.go

@ -36,6 +36,10 @@ type ClientCommonConf struct {
LogLevel string
LogMaxDays int64
PrivilegeToken string
AdminAddr string
AdminPort int64
AdminUser string
AdminPwd string
PoolCount int
TcpMux bool
User string
@ -57,6 +61,10 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
LogLevel: "info",
LogMaxDays: 3,
PrivilegeToken: "",
AdminAddr: "127.0.0.1",
AdminPort: 0,
AdminUser: "",
AdminPwd: "",
PoolCount: 1,
TcpMux: true,
User: "",
@ -111,7 +119,9 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_token")
@ -119,6 +129,28 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
cfg.PrivilegeToken = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_addr")
if ok {
cfg.AdminAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_port")
if ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
cfg.AdminPort = v
}
}
tmpStr, ok = conf.Get("common", "admin_user")
if ok {
cfg.AdminUser = tmpStr
}
tmpStr, ok = conf.Get("common", "admin_pwd")
if ok {
cfg.AdminPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
@ -145,7 +177,7 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
if ok {
proxyNames := strings.Split(tmpStr, ",")
for _, name := range proxyNames {
cfg.Start[name] = struct{}{}
cfg.Start[strings.TrimSpace(name)] = struct{}{}
}
}

233
models/config/proxy.go

@ -35,6 +35,7 @@ func init() {
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
}
// NewConfByType creates a empty ProxyConf object by proxyType.
@ -55,6 +56,7 @@ type ProxyConf interface {
LoadFromFile(name string, conf ini.Section) error
UnMarshalToMsg(pMsg *msg.NewProxy)
Check() error
Compare(conf ProxyConf) bool
}
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
@ -104,6 +106,16 @@ func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
return cfg
}
func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
if cfg.ProxyName != cmp.ProxyName ||
cfg.ProxyType != cmp.ProxyType ||
cfg.UseEncryption != cmp.UseEncryption ||
cfg.UseCompression != cmp.UseCompression {
return false
}
return true
}
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName
cfg.ProxyType = pMsg.ProxyType
@ -148,8 +160,16 @@ type BindInfoConf struct {
RemotePort int64 `json:"remote_port"`
}
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
if cfg.BindAddr != cmp.BindAddr ||
cfg.RemotePort != cmp.RemotePort {
return false
}
return true
}
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BindAddr = ServerCommonCfg.BindAddr
cfg.BindAddr = ServerCommonCfg.ProxyBindAddr
cfg.RemotePort = pMsg.RemotePort
}
@ -187,6 +207,14 @@ type DomainConf struct {
SubDomain string `json:"sub_domain"`
}
func (cfg *DomainConf) compare(cmp *DomainConf) bool {
if strings.Join(cfg.CustomDomains, " ") != strings.Join(cmp.CustomDomains, " ") ||
cfg.SubDomain != cmp.SubDomain {
return false
}
return true
}
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain
@ -245,6 +273,14 @@ type LocalSvrConf struct {
LocalPort int `json:"-"`
}
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
if cfg.LocalIp != cmp.LocalIp ||
cfg.LocalPort != cmp.LocalPort {
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"
@ -265,6 +301,20 @@ type PluginConf struct {
PluginParams map[string]string `json:"-"`
}
func (cfg *PluginConf) compare(cmp *PluginConf) bool {
if cfg.Plugin != cmp.Plugin ||
len(cfg.PluginParams) != len(cmp.PluginParams) {
return false
}
for k, v := range cfg.PluginParams {
value, ok := cmp.PluginParams[k]
if !ok || v != value {
return false
}
}
return true
}
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
cfg.Plugin = section["plugin"]
cfg.PluginParams = make(map[string]string)
@ -290,6 +340,21 @@ type TcpProxyConf struct {
PluginConf
}
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TcpProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
return false
}
return true
}
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
@ -329,6 +394,20 @@ type UdpProxyConf struct {
LocalSvrConf
}
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*UdpProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
return false
}
return true
}
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
@ -371,6 +450,25 @@ type HttpProxyConf struct {
HttpPwd string `json:"-"`
}
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HttpProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.HttpUser != cmpConf.HttpUser ||
cfg.HttpPwd != cmpConf.HttpPwd {
return false
}
return true
}
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
@ -388,8 +486,10 @@ func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err er
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
}
var (
@ -435,6 +535,21 @@ type HttpsProxyConf struct {
PluginConf
}
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HttpsProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
return false
}
return true
}
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
@ -447,8 +562,10 @@ func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err e
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
}
return
}
@ -466,9 +583,100 @@ func (cfg *HttpsProxyConf) Check() (err error) {
return
}
// STCP
type StcpProxyConf struct {
BaseProxyConf
Role string `json:"role"`
Sk string `json:"sk"`
// used in role server
LocalSvrConf
PluginConf
// used in role vistor
ServerName string `json:"server_name"`
BindAddr string `json:"bind_addr"`
BindPort int `json:"bind_port"`
}
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*StcpProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk ||
cfg.ServerName != cmpConf.ServerName ||
cfg.BindAddr != cmpConf.BindAddr ||
cfg.BindPort != cmpConf.BindPort {
return false
}
return true
}
// Only for role server.
func (cfg *StcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
tmpStr := section["role"]
if tmpStr == "server" || tmpStr == "vistor" {
cfg.Role = tmpStr
} else {
cfg.Role = "server"
}
cfg.Sk = section["sk"]
if tmpStr == "vistor" {
prefix := section["prefix"]
cfg.ServerName = prefix + section["server_name"]
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
cfg.BindAddr = "127.0.0.1"
}
if tmpStr, ok := section["bind_port"]; ok {
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
}
} else {
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
}
}
return
}
func (cfg *StcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *StcpProxyConf) Check() (err error) {
return
}
// if len(startProxy) is 0, start all
// otherwise just start proxies in startProxy map
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (proxyConfs map[string]ProxyConf, err error) {
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
proxyConfs map[string]ProxyConf, vistorConfs map[string]ProxyConf, err error) {
if prefix != "" {
prefix += "."
}
@ -478,14 +686,23 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
startAll = false
}
proxyConfs = make(map[string]ProxyConf)
vistorConfs = make(map[string]ProxyConf)
for name, section := range conf {
_, shouldStart := startProxy[name]
if name != "common" && (startAll || shouldStart) {
// some proxy or visotr configure may be used this prefix
section["prefix"] = prefix
cfg, err := NewProxyConfFromFile(name, section)
if err != nil {
return proxyConfs, err
return proxyConfs, vistorConfs, err
}
role := section["role"]
if role == "vistor" {
vistorConfs[prefix+name] = cfg
} else {
proxyConfs[prefix+name] = cfg
}
proxyConfs[prefix+name] = cfg
}
}
return

17
models/config/server_common.go

@ -27,10 +27,11 @@ var ServerCommonCfg *ServerCommonConf
// common config
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
KcpBindPort int64
ConfigFile string
BindAddr string
BindPort int64
KcpBindPort int64
ProxyBindAddr string
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
@ -66,6 +67,7 @@ func GetDefaultServerCommonConf() *ServerCommonConf {
BindAddr: "0.0.0.0",
BindPort: 7000,
KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardPort: 0,
@ -117,6 +119,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
}
}
tmpStr, ok = conf.Get("common", "proxy_bind_addr")
if ok {
cfg.ProxyBindAddr = tmpStr
} else {
cfg.ProxyBindAddr = cfg.BindAddr
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)

1
models/consts/consts.go

@ -27,4 +27,5 @@ var (
UdpProxy string = "udp"
HttpProxy string = "http"
HttpsProxy string = "https"
StcpProxy string = "stcp"
)

42
models/msg/msg.go

@ -20,17 +20,19 @@ import (
)
const (
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeCloseProxy = 'c'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeCloseProxy = 'c'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypeNewVistorConn = 'v'
TypeNewVistorConnResp = '3'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
)
var (
@ -50,6 +52,8 @@ func init() {
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
TypeMap[TypeNewVistorConn] = reflect.TypeOf(NewVistorConn{})
TypeMap[TypeNewVistorConnResp] = reflect.TypeOf(NewVistorConnResp{})
TypeMap[TypePing] = reflect.TypeOf(Ping{})
TypeMap[TypePong] = reflect.TypeOf(Pong{})
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
@ -100,6 +104,9 @@ type NewProxy struct {
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
// stcp
Sk string `json:"sk"`
}
type NewProxyResp struct {
@ -122,6 +129,19 @@ type StartWorkConn struct {
ProxyName string `json:"proxy_name"`
}
type NewVistorConn struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
}
type NewVistorConnResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
}
type Ping struct {
}

65
models/plugin/socks5.go

@ -0,0 +1,65 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package plugin
import (
"io"
"io/ioutil"
"log"
frpNet "github.com/fatedier/frp/utils/net"
gosocks5 "github.com/armon/go-socks5"
)
const PluginSocks5 = "socks5"
func init() {
Register(PluginSocks5, NewSocks5Plugin)
}
type Socks5Plugin struct {
Server *gosocks5.Server
}
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(&gosocks5.Config{
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
})
p = sp
return
}
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser) {
defer conn.Close()
var wrapConn frpNet.Conn
if realConn, ok := conn.(frpNet.Conn); ok {
wrapConn = realConn
} else {
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
}
sp.Server.ServeConn(wrapConn)
}
func (sp *Socks5Plugin) Name() string {
return PluginSocks5
}
func (sp *Socks5Plugin) Close() error {
return nil
}

1
server/control.go

@ -378,6 +378,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
delete(ctl.proxies, closeMsg.ProxyName)
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
return
}

114
server/dashboard.go

@ -15,16 +15,14 @@
package server
import (
"compress/gzip"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/julienschmidt/httprouter"
)
@ -38,20 +36,24 @@ func RunDashboardServer(addr string, port int64) (err error) {
// url router
router := httprouter.New()
user, passwd := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd
// api, see dashboard_api.go
router.GET("/api/serverinfo", httprouterBasicAuth(apiServerInfo))
router.GET("/api/proxy/tcp", httprouterBasicAuth(apiProxyTcp))
router.GET("/api/proxy/udp", httprouterBasicAuth(apiProxyUdp))
router.GET("/api/proxy/http", httprouterBasicAuth(apiProxyHttp))
router.GET("/api/proxy/https", httprouterBasicAuth(apiProxyHttps))
router.GET("/api/proxy/traffic/:name", httprouterBasicAuth(apiProxyTraffic))
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, 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/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))
router.GET("/api/proxy/https", frpNet.HttprouterBasicAuth(apiProxyHttps, user, passwd))
router.GET("/api/proxy/traffic/:name", frpNet.HttprouterBasicAuth(apiProxyTraffic, user, passwd))
// view
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static/*filepath", MakeGzipHandler(basicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))))
router.HandlerFunc("GET", "/", basicAuth(func(w http.ResponseWriter, r *http.Request) {
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler(
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
router.HandlerFunc("GET", "/", frpNet.HttpBasicAuth(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}))
}, user, passwd))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
@ -71,91 +73,3 @@ func RunDashboardServer(addr string, port int64) (err error) {
go server.Serve(ln)
return
}
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
type AuthWraper struct {
h http.Handler
user string
passwd string
}
func (aw *AuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
aw.h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
func basicAuthWraper(h http.Handler) http.Handler {
return &AuthWraper{
h: h,
user: config.ServerCommonCfg.DashboardUser,
passwd: config.ServerCommonCfg.DashboardPwd,
}
}
func basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func httprouterBasicAuth(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user, passwd, hasAuth := r.BasicAuth()
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
h(w, r, ps)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
type GzipWraper struct {
h http.Handler
}
func (gw *GzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
gw.h.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
gw.h.ServeHTTP(gzr, r)
}
func MakeGzipHandler(h http.Handler) http.Handler {
return &GzipWraper{
h: h,
}
}
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}

74
server/manager.go

@ -16,7 +16,12 @@ package server
import (
"fmt"
"io"
"sync"
frpIo "github.com/fatedier/frp/utils/io"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
)
type ControlManager struct {
@ -87,3 +92,72 @@ func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
pxy, ok = pm.pxys[name]
return
}
// Manager for vistor listeners.
type VistorManager struct {
vistorListeners map[string]*frpNet.CustomListener
skMap map[string]string
mu sync.RWMutex
}
func NewVistorManager() *VistorManager {
return &VistorManager{
vistorListeners: make(map[string]*frpNet.CustomListener),
skMap: make(map[string]string),
}
}
func (vm *VistorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
vm.mu.Lock()
defer vm.mu.Unlock()
if _, ok := vm.vistorListeners[name]; ok {
err = fmt.Errorf("custom listener for [%s] is repeated", name)
return
}
l = frpNet.NewCustomListener()
vm.vistorListeners[name] = l
vm.skMap[name] = sk
return
}
func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
useEncryption bool, useCompression bool) (err error) {
vm.mu.RLock()
defer vm.mu.RUnlock()
if l, ok := vm.vistorListeners[name]; ok {
var sk string
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
err = fmt.Errorf("vistor connection of [%s] auth failed", name)
return
}
var rwc io.ReadWriteCloser = conn
if useEncryption {
if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil {
err = fmt.Errorf("create encryption connection failed: %v", err)
return
}
}
if useCompression {
rwc = frpIo.WithCompression(rwc)
}
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc))
} else {
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
return
}
return
}
func (vm *VistorManager) CloseListener(name string) {
vm.mu.Lock()
defer vm.mu.Unlock()
delete(vm.vistorListeners, name)
delete(vm.skMap, name)
}

36
server/proxy.go

@ -143,6 +143,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
BaseProxy: basePxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
@ -156,7 +161,7 @@ type TcpProxy struct {
}
func (pxy *TcpProxy) Run() error {
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort)
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if err != nil {
return err
}
@ -274,6 +279,33 @@ func (pxy *HttpsProxy) Close() {
pxy.BaseProxy.Close()
}
type StcpProxy struct {
BaseProxy
cfg *config.StcpProxyConf
}
func (pxy *StcpProxy) Run() error {
listener, err := pxy.ctl.svr.vistorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if err != nil {
return err
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
}
func (pxy *StcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *StcpProxy) Close() {
pxy.BaseProxy.Close()
pxy.ctl.svr.vistorManager.CloseListener(pxy.GetName())
}
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
@ -298,7 +330,7 @@ type UdpProxy struct {
}
func (pxy *UdpProxy) Run() (err error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort))
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort))
if err != nil {
return err
}

38
server/service.go

@ -55,12 +55,16 @@ type Service struct {
// Manage all proxies.
pxyManager *ProxyManager
// Manage all vistor listeners.
vistorManager *VistorManager
}
func NewService() (svr *Service, err error) {
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
vistorManager: NewVistorManager(),
}
// Init assets.
@ -91,7 +95,7 @@ func NewService() (svr *Service, err error) {
// Create http vhost muxer.
if config.ServerCommonCfg.VhostHttpPort > 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
@ -101,13 +105,13 @@ func NewService() (svr *Service, err error) {
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
return
}
log.Info("http service listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
log.Info("http service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
}
// Create https vhost muxer.
if config.ServerCommonCfg.VhostHttpsPort > 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
if err != nil {
err = fmt.Errorf("Create vhost https listener error, %v", err)
return
@ -117,7 +121,7 @@ func NewService() (svr *Service, err error) {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
}
log.Info("https service listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
log.Info("https service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
}
// Create dashboard web server.
@ -176,6 +180,20 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
}
case *msg.NewWorkConn:
svr.RegisterWorkConn(conn, m)
case *msg.NewVistorConn:
if err = svr.RegisterVistorConn(conn, m); err != nil {
conn.Warn("%v", err)
msg.WriteMsg(conn, &msg.NewVistorConnResp{
ProxyName: m.ProxyName,
Error: err.Error(),
})
conn.Close()
} else {
msg.WriteMsg(conn, &msg.NewVistorConnResp{
ProxyName: m.ProxyName,
Error: "",
})
}
default:
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
conn.Close()
@ -262,9 +280,13 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
return
}
func (svr *Service) RegisterVistorConn(vistorConn frpNet.Conn, newMsg *msg.NewVistorConn) error {
return svr.vistorManager.NewConn(newMsg.ProxyName, vistorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression)
}
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
err := svr.pxyManager.Add(name, pxy)
return err
return svr.pxyManager.Add(name, pxy)
}
func (svr *Service) DelProxy(name string) {

5
utils/log/log.go

@ -88,6 +88,7 @@ func Trace(format string, v ...interface{}) {
// Logger
type Logger interface {
AddLogPrefix(string)
GetPrefixStr() string
GetAllPrefix() []string
ClearLogPrefix()
Error(string, ...interface{})
@ -119,6 +120,10 @@ func (pl *PrefixLogger) AddLogPrefix(prefix string) {
pl.allPrefix = append(pl.allPrefix, prefix)
}
func (pl *PrefixLogger) GetPrefixStr() string {
return pl.prefix
}
func (pl *PrefixLogger) GetAllPrefix() []string {
return pl.allPrefix
}

105
utils/net/http.go

@ -0,0 +1,105 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net
import (
"compress/gzip"
"io"
"net/http"
"strings"
"github.com/julienschmidt/httprouter"
)
type HttpAuthWraper struct {
h http.Handler
user string
passwd string
}
func NewHttpBasicAuthWraper(h http.Handler, user, passwd string) http.Handler {
return &HttpAuthWraper{
h: h,
user: user,
passwd: passwd,
}
}
func (aw *HttpAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
aw.h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
func HttpBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (user == "" && passwd == "") ||
(hasAuth && reqUser == user && reqPasswd == passwd) {
h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func HttprouterBasicAuth(h httprouter.Handle, user, passwd string) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (user == "" && passwd == "") ||
(hasAuth && reqUser == user && reqPasswd == passwd) {
h(w, r, ps)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
type HttpGzipWraper struct {
h http.Handler
}
func (gw *HttpGzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
gw.h.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
gw.h.ServeHTTP(gzr, r)
}
func MakeHttpGzipHandler(h http.Handler) http.Handler {
return &HttpGzipWraper{
h: h,
}
}
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}

53
utils/net/listener.go

@ -15,8 +15,11 @@
package net
import (
"fmt"
"net"
"sync"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
)
@ -44,3 +47,53 @@ func (logL *LogListener) Accept() (Conn, error) {
c, err := logL.l.Accept()
return WrapConn(c), err
}
// Custom listener
type CustomListener struct {
conns chan Conn
closed bool
mu sync.Mutex
log.Logger
}
func NewCustomListener() *CustomListener {
return &CustomListener{
conns: make(chan Conn, 64),
Logger: log.NewPrefixLogger(""),
}
}
func (l *CustomListener) Accept() (Conn, error) {
conn, ok := <-l.conns
if !ok {
return nil, fmt.Errorf("listener closed")
}
conn.AddLogPrefix(l.GetPrefixStr())
return conn, nil
}
func (l *CustomListener) PutConn(conn Conn) error {
err := errors.PanicToError(func() {
select {
case l.conns <- conn:
default:
conn.Close()
}
})
return err
}
func (l *CustomListener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if !l.closed {
close(l.conns)
l.closed = true
}
return nil
}
func (l *CustomListener) Addr() net.Addr {
return (*net.TCPAddr)(nil)
}

2
utils/version/version.go

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

51
utils/vhost/http.go

@ -57,30 +57,35 @@ func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err
}
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, HttpHostNameRewrite, timeout)
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
return &HttpMuxer{mux}, err
}
func HttpHostNameRewrite(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
sc, rd := frpNet.NewShareConn(c)
var buff []byte
if buff, err = hostNameRewrite(rd, rewriteHost); err != nil {
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
return sc, err
}
err = sc.WriteBuff(buff)
return sc, err
}
func hostNameRewrite(request io.Reader, rewriteHost string) (_ []byte, err error) {
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
buf := pool.GetBuf(1024)
defer pool.PutBuf(buf)
request.Read(buf)
retBuffer, err := parseRequest(buf, rewriteHost)
var n int
n, err = request.Read(buf)
if err != nil {
return
}
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
return retBuffer, err
}
func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) {
tp := bytes.NewBuffer(org)
// First line: GET /index.html HTTP/1.0
var b []byte
@ -106,10 +111,19 @@ func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
// GET /index.html HTTP/1.1
// Host: www.google.com
if req.URL.Host == "" {
changedBuf, err := changeHostName(tp, rewriteHost)
var changedBuf []byte
if rewriteHost != "" {
changedBuf, err = changeHostName(tp, rewriteHost)
}
buf := new(bytes.Buffer)
buf.Write(b)
buf.Write(changedBuf)
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
if len(changedBuf) == 0 {
tp.WriteTo(buf)
} else {
buf.Write(changedBuf)
}
return buf.Bytes(), err
}
@ -117,18 +131,21 @@ func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
// GET http://www.google.com/index.html HTTP/1.1
// Host: doesntmatter
// In this case, any Host line is ignored.
hostPort := strings.Split(req.URL.Host, ":")
if len(hostPort) == 1 {
req.URL.Host = rewriteHost
} else if len(hostPort) == 2 {
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
if rewriteHost != "" {
hostPort := strings.Split(req.URL.Host, ":")
if len(hostPort) == 1 {
req.URL.Host = rewriteHost
} else if len(hostPort) == 2 {
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
}
}
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
buf := new(bytes.Buffer)
buf.WriteString(firstLine)
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
tp.WriteTo(buf)
return buf.Bytes(), err
}
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
@ -162,9 +179,9 @@ func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error
var hostHeader string
portPos := bytes.IndexByte(kv[j+1:], ':')
if portPos == -1 {
hostHeader = fmt.Sprintf("Host: %s\n", rewriteHost)
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
} else {
hostHeader = fmt.Sprintf("Host: %s:%s\n", rewriteHost, kv[portPos+1:])
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
}
retBuf.WriteString(hostHeader)
peek = peek[i+1:]

3
utils/vhost/resource.go

@ -49,9 +49,10 @@ Please try again later.</p>
func notFoundResponse() *http.Response {
header := make(http.Header)
header.Set("server", "frp/"+version.Full())
header.Set("Content-Type", "text/html")
res := &http.Response{
Status: "Not Found",
StatusCode: 400,
StatusCode: 404,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,

5
utils/vhost/vhost.go

@ -182,9 +182,10 @@ func (l *Listener) Accept() (frpNet.Conn, error) {
return nil, fmt.Errorf("Listener closed")
}
// if rewriteFunc is exist and rewriteHost is set
// if rewriteFunc is exist
// rewrite http requests with a modified host header
if l.mux.rewriteFunc != nil && l.rewriteHost != "" {
// if l.rewriteHost is empty, nothing to do
if l.mux.rewriteFunc != nil {
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
if err != nil {
l.Warn("host header rewrite failed: %v", err)

22
vendor/github.com/armon/go-socks5/.gitignore generated vendored

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

4
vendor/github.com/armon/go-socks5/.travis.yml generated vendored

@ -0,0 +1,4 @@
language: go
go:
- 1.1
- tip

20
vendor/github.com/armon/go-socks5/LICENSE generated vendored

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Armon Dadgar
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.

45
vendor/github.com/armon/go-socks5/README.md generated vendored

@ -0,0 +1,45 @@
go-socks5 [![Build Status](https://travis-ci.org/armon/go-socks5.png)](https://travis-ci.org/armon/go-socks5)
=========
Provides the `socks5` package that implements a [SOCKS5 server](http://en.wikipedia.org/wiki/SOCKS).
SOCKS (Secure Sockets) is used to route traffic between a client and server through
an intermediate proxy layer. This can be used to bypass firewalls or NATs.
Feature
=======
The package has the following features:
* "No Auth" mode
* User/Password authentication
* Support for the CONNECT command
* Rules to do granular filtering of commands
* Custom DNS resolution
* Unit tests
TODO
====
The package still needs the following:
* Support for the BIND command
* Support for the ASSOCIATE command
Example
=======
Below is a simple example of usage
```go
// Create a SOCKS5 server
conf := &socks5.Config{}
server, err := socks5.New(conf)
if err != nil {
panic(err)
}
// Create SOCKS5 proxy on localhost port 8000
if err := server.ListenAndServe("tcp", "127.0.0.1:8000"); err != nil {
panic(err)
}
```

151
vendor/github.com/armon/go-socks5/auth.go generated vendored

@ -0,0 +1,151 @@
package socks5
import (
"fmt"
"io"
)
const (
NoAuth = uint8(0)
noAcceptable = uint8(255)
UserPassAuth = uint8(2)
userAuthVersion = uint8(1)
authSuccess = uint8(0)
authFailure = uint8(1)
)
var (
UserAuthFailed = fmt.Errorf("User authentication failed")
NoSupportedAuth = fmt.Errorf("No supported authentication mechanism")
)
// A Request encapsulates authentication state provided
// during negotiation
type AuthContext struct {
// Provided auth method
Method uint8
// Payload provided during negotiation.
// Keys depend on the used auth method.
// For UserPassauth contains Username
Payload map[string]string
}
type Authenticator interface {
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
GetCode() uint8
}
// NoAuthAuthenticator is used to handle the "No Authentication" mode
type NoAuthAuthenticator struct{}
func (a NoAuthAuthenticator) GetCode() uint8 {
return NoAuth
}
func (a NoAuthAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
_, err := writer.Write([]byte{socks5Version, NoAuth})
return &AuthContext{NoAuth, nil}, err
}
// UserPassAuthenticator is used to handle username/password based
// authentication
type UserPassAuthenticator struct {
Credentials CredentialStore
}
func (a UserPassAuthenticator) GetCode() uint8 {
return UserPassAuth
}
func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
// Tell the client to use user/pass auth
if _, err := writer.Write([]byte{socks5Version, UserPassAuth}); err != nil {
return nil, err
}
// Get the version and username length
header := []byte{0, 0}
if _, err := io.ReadAtLeast(reader, header, 2); err != nil {
return nil, err
}
// Ensure we are compatible
if header[0] != userAuthVersion {
return nil, fmt.Errorf("Unsupported auth version: %v", header[0])
}
// Get the user name
userLen := int(header[1])
user := make([]byte, userLen)
if _, err := io.ReadAtLeast(reader, user, userLen); err != nil {
return nil, err
}
// Get the password length
if _, err := reader.Read(header[:1]); err != nil {
return nil, err
}
// Get the password
passLen := int(header[0])
pass := make([]byte, passLen)
if _, err := io.ReadAtLeast(reader, pass, passLen); err != nil {
return nil, err
}
// Verify the password
if a.Credentials.Valid(string(user), string(pass)) {
if _, err := writer.Write([]byte{userAuthVersion, authSuccess}); err != nil {
return nil, err
}
} else {
if _, err := writer.Write([]byte{userAuthVersion, authFailure}); err != nil {
return nil, err
}
return nil, UserAuthFailed
}
// Done
return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil
}
// authenticate is used to handle connection authentication
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) (*AuthContext, error) {
// Get the methods
methods, err := readMethods(bufConn)
if err != nil {
return nil, fmt.Errorf("Failed to get auth methods: %v", err)
}
// Select a usable method
for _, method := range methods {
cator, found := s.authMethods[method]
if found {
return cator.Authenticate(bufConn, conn)
}
}
// No usable method found
return nil, noAcceptableAuth(conn)
}
// noAcceptableAuth is used to handle when we have no eligible
// authentication mechanism
func noAcceptableAuth(conn io.Writer) error {
conn.Write([]byte{socks5Version, noAcceptable})
return NoSupportedAuth
}
// readMethods is used to read the number of methods
// and proceeding auth methods
func readMethods(r io.Reader) ([]byte, error) {
header := []byte{0}
if _, err := r.Read(header); err != nil {
return nil, err
}
numMethods := int(header[0])
methods := make([]byte, numMethods)
_, err := io.ReadAtLeast(r, methods, numMethods)
return methods, err
}

17
vendor/github.com/armon/go-socks5/credentials.go generated vendored

@ -0,0 +1,17 @@
package socks5
// CredentialStore is used to support user/pass authentication
type CredentialStore interface {
Valid(user, password string) bool
}
// StaticCredentials enables using a map directly as a credential store
type StaticCredentials map[string]string
func (s StaticCredentials) Valid(user, password string) bool {
pass, ok := s[user]
if !ok {
return false
}
return password == pass
}

364
vendor/github.com/armon/go-socks5/request.go generated vendored

@ -0,0 +1,364 @@
package socks5
import (
"fmt"
"io"
"net"
"strconv"
"strings"
"golang.org/x/net/context"
)
const (
ConnectCommand = uint8(1)
BindCommand = uint8(2)
AssociateCommand = uint8(3)
ipv4Address = uint8(1)
fqdnAddress = uint8(3)
ipv6Address = uint8(4)
)
const (
successReply uint8 = iota
serverFailure
ruleFailure
networkUnreachable
hostUnreachable
connectionRefused
ttlExpired
commandNotSupported
addrTypeNotSupported
)
var (
unrecognizedAddrType = fmt.Errorf("Unrecognized address type")
)
// AddressRewriter is used to rewrite a destination transparently
type AddressRewriter interface {
Rewrite(ctx context.Context, request *Request) (context.Context, *AddrSpec)
}
// AddrSpec is used to return the target AddrSpec
// which may be specified as IPv4, IPv6, or a FQDN
type AddrSpec struct {
FQDN string
IP net.IP
Port int
}
func (a *AddrSpec) String() string {
if a.FQDN != "" {
return fmt.Sprintf("%s (%s):%d", a.FQDN, a.IP, a.Port)
}
return fmt.Sprintf("%s:%d", a.IP, a.Port)
}
// Address returns a string suitable to dial; prefer returning IP-based
// address, fallback to FQDN
func (a AddrSpec) Address() string {
if 0 != len(a.IP) {
return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
}
return net.JoinHostPort(a.FQDN, strconv.Itoa(a.Port))
}
// A Request represents request received by a server
type Request struct {
// Protocol version
Version uint8
// Requested command
Command uint8
// AuthContext provided during negotiation
AuthContext *AuthContext
// AddrSpec of the the network that sent the request
RemoteAddr *AddrSpec
// AddrSpec of the desired destination
DestAddr *AddrSpec
// AddrSpec of the actual destination (might be affected by rewrite)
realDestAddr *AddrSpec
bufConn io.Reader
}
type conn interface {
Write([]byte) (int, error)
RemoteAddr() net.Addr
}
// NewRequest creates a new Request from the tcp connection
func NewRequest(bufConn io.Reader) (*Request, error) {
// Read the version byte
header := []byte{0, 0, 0}
if _, err := io.ReadAtLeast(bufConn, header, 3); err != nil {
return nil, fmt.Errorf("Failed to get command version: %v", err)
}
// Ensure we are compatible
if header[0] != socks5Version {
return nil, fmt.Errorf("Unsupported command version: %v", header[0])
}
// Read in the destination address
dest, err := readAddrSpec(bufConn)
if err != nil {
return nil, err
}
request := &Request{
Version: socks5Version,
Command: header[1],
DestAddr: dest,
bufConn: bufConn,
}
return request, nil
}
// handleRequest is used for request processing after authentication
func (s *Server) handleRequest(req *Request, conn conn) error {
ctx := context.Background()
// Resolve the address if we have a FQDN
dest := req.DestAddr
if dest.FQDN != "" {
ctx_, addr, err := s.config.Resolver.Resolve(ctx, dest.FQDN)
if err != nil {
if err := sendReply(conn, hostUnreachable, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Failed to resolve destination '%v': %v", dest.FQDN, err)
}
ctx = ctx_
dest.IP = addr
}
// Apply any address rewrites
req.realDestAddr = req.DestAddr
if s.config.Rewriter != nil {
ctx, req.realDestAddr = s.config.Rewriter.Rewrite(ctx, req)
}
// Switch on the command
switch req.Command {
case ConnectCommand:
return s.handleConnect(ctx, conn, req)
case BindCommand:
return s.handleBind(ctx, conn, req)
case AssociateCommand:
return s.handleAssociate(ctx, conn, req)
default:
if err := sendReply(conn, commandNotSupported, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Unsupported command: %v", req.Command)
}
}
// handleConnect is used to handle a connect command
func (s *Server) handleConnect(ctx context.Context, conn conn, req *Request) error {
// Check if this is allowed
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
if err := sendReply(conn, ruleFailure, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Connect to %v blocked by rules", req.DestAddr)
} else {
ctx = ctx_
}
// Attempt to connect
dial := s.config.Dial
if dial == nil {
dial = func(ctx context.Context, net_, addr string) (net.Conn, error) {
return net.Dial(net_, addr)
}
}
target, err := dial(ctx, "tcp", req.realDestAddr.Address())
if err != nil {
msg := err.Error()
resp := hostUnreachable
if strings.Contains(msg, "refused") {
resp = connectionRefused
} else if strings.Contains(msg, "network is unreachable") {
resp = networkUnreachable
}
if err := sendReply(conn, resp, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Connect to %v failed: %v", req.DestAddr, err)
}
defer target.Close()
// Send success
local := target.LocalAddr().(*net.TCPAddr)
bind := AddrSpec{IP: local.IP, Port: local.Port}
if err := sendReply(conn, successReply, &bind); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
// Start proxying
errCh := make(chan error, 2)
go proxy(target, req.bufConn, errCh)
go proxy(conn, target, errCh)
// Wait
for i := 0; i < 2; i++ {
e := <-errCh
if e != nil {
// return from this function closes target (and conn).
return e
}
}
return nil
}
// handleBind is used to handle a connect command
func (s *Server) handleBind(ctx context.Context, conn conn, req *Request) error {
// Check if this is allowed
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
if err := sendReply(conn, ruleFailure, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Bind to %v blocked by rules", req.DestAddr)
} else {
ctx = ctx_
}
// TODO: Support bind
if err := sendReply(conn, commandNotSupported, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return nil
}
// handleAssociate is used to handle a connect command
func (s *Server) handleAssociate(ctx context.Context, conn conn, req *Request) error {
// Check if this is allowed
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
if err := sendReply(conn, ruleFailure, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return fmt.Errorf("Associate to %v blocked by rules", req.DestAddr)
} else {
ctx = ctx_
}
// TODO: Support associate
if err := sendReply(conn, commandNotSupported, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
return nil
}
// readAddrSpec is used to read AddrSpec.
// Expects an address type byte, follwed by the address and port
func readAddrSpec(r io.Reader) (*AddrSpec, error) {
d := &AddrSpec{}
// Get the address type
addrType := []byte{0}
if _, err := r.Read(addrType); err != nil {
return nil, err
}
// Handle on a per type basis
switch addrType[0] {
case ipv4Address:
addr := make([]byte, 4)
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
return nil, err
}
d.IP = net.IP(addr)
case ipv6Address:
addr := make([]byte, 16)
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
return nil, err
}
d.IP = net.IP(addr)
case fqdnAddress:
if _, err := r.Read(addrType); err != nil {
return nil, err
}
addrLen := int(addrType[0])
fqdn := make([]byte, addrLen)
if _, err := io.ReadAtLeast(r, fqdn, addrLen); err != nil {
return nil, err
}
d.FQDN = string(fqdn)
default:
return nil, unrecognizedAddrType
}
// Read the port
port := []byte{0, 0}
if _, err := io.ReadAtLeast(r, port, 2); err != nil {
return nil, err
}
d.Port = (int(port[0]) << 8) | int(port[1])
return d, nil
}
// sendReply is used to send a reply message
func sendReply(w io.Writer, resp uint8, addr *AddrSpec) error {
// Format the address
var addrType uint8
var addrBody []byte
var addrPort uint16
switch {
case addr == nil:
addrType = ipv4Address
addrBody = []byte{0, 0, 0, 0}
addrPort = 0
case addr.FQDN != "":
addrType = fqdnAddress
addrBody = append([]byte{byte(len(addr.FQDN))}, addr.FQDN...)
addrPort = uint16(addr.Port)
case addr.IP.To4() != nil:
addrType = ipv4Address
addrBody = []byte(addr.IP.To4())
addrPort = uint16(addr.Port)
case addr.IP.To16() != nil:
addrType = ipv6Address
addrBody = []byte(addr.IP.To16())
addrPort = uint16(addr.Port)
default:
return fmt.Errorf("Failed to format address: %v", addr)
}
// Format the message
msg := make([]byte, 6+len(addrBody))
msg[0] = socks5Version
msg[1] = resp
msg[2] = 0 // Reserved
msg[3] = addrType
copy(msg[4:], addrBody)
msg[4+len(addrBody)] = byte(addrPort >> 8)
msg[4+len(addrBody)+1] = byte(addrPort & 0xff)
// Send the message
_, err := w.Write(msg)
return err
}
type closeWriter interface {
CloseWrite() error
}
// proxy is used to suffle data from src to destination, and sends errors
// down a dedicated channel
func proxy(dst io.Writer, src io.Reader, errCh chan error) {
_, err := io.Copy(dst, src)
if tcpConn, ok := dst.(closeWriter); ok {
tcpConn.CloseWrite()
}
errCh <- err
}

23
vendor/github.com/armon/go-socks5/resolver.go generated vendored

@ -0,0 +1,23 @@
package socks5
import (
"net"
"golang.org/x/net/context"
)
// NameResolver is used to implement custom name resolution
type NameResolver interface {
Resolve(ctx context.Context, name string) (context.Context, net.IP, error)
}
// DNSResolver uses the system DNS to resolve host names
type DNSResolver struct{}
func (d DNSResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
addr, err := net.ResolveIPAddr("ip", name)
if err != nil {
return ctx, nil, err
}
return ctx, addr.IP, err
}

41
vendor/github.com/armon/go-socks5/ruleset.go generated vendored

@ -0,0 +1,41 @@
package socks5
import (
"golang.org/x/net/context"
)
// RuleSet is used to provide custom rules to allow or prohibit actions
type RuleSet interface {
Allow(ctx context.Context, req *Request) (context.Context, bool)
}
// PermitAll returns a RuleSet which allows all types of connections
func PermitAll() RuleSet {
return &PermitCommand{true, true, true}
}
// PermitNone returns a RuleSet which disallows all types of connections
func PermitNone() RuleSet {
return &PermitCommand{false, false, false}
}
// PermitCommand is an implementation of the RuleSet which
// enables filtering supported commands
type PermitCommand struct {
EnableConnect bool
EnableBind bool
EnableAssociate bool
}
func (p *PermitCommand) Allow(ctx context.Context, req *Request) (context.Context, bool) {
switch req.Command {
case ConnectCommand:
return ctx, p.EnableConnect
case BindCommand:
return ctx, p.EnableBind
case AssociateCommand:
return ctx, p.EnableAssociate
}
return ctx, false
}

169
vendor/github.com/armon/go-socks5/socks5.go generated vendored

@ -0,0 +1,169 @@
package socks5
import (
"bufio"
"fmt"
"log"
"net"
"os"
"golang.org/x/net/context"
)
const (
socks5Version = uint8(5)
)
// Config is used to setup and configure a Server
type Config struct {
// AuthMethods can be provided to implement custom authentication
// By default, "auth-less" mode is enabled.
// For password-based auth use UserPassAuthenticator.
AuthMethods []Authenticator
// If provided, username/password authentication is enabled,
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
// and AUthMethods is nil, then "auth-less" mode is enabled.
Credentials CredentialStore
// Resolver can be provided to do custom name resolution.
// Defaults to DNSResolver if not provided.
Resolver NameResolver
// Rules is provided to enable custom logic around permitting
// various commands. If not provided, PermitAll is used.
Rules RuleSet
// Rewriter can be used to transparently rewrite addresses.
// This is invoked before the RuleSet is invoked.
// Defaults to NoRewrite.
Rewriter AddressRewriter
// BindIP is used for bind or udp associate
BindIP net.IP
// Logger can be used to provide a custom log target.
// Defaults to stdout.
Logger *log.Logger
// Optional function for dialing out
Dial func(ctx context.Context, network, addr string) (net.Conn, error)
}
// Server is reponsible for accepting connections and handling
// the details of the SOCKS5 protocol
type Server struct {
config *Config
authMethods map[uint8]Authenticator
}
// New creates a new Server and potentially returns an error
func New(conf *Config) (*Server, error) {
// Ensure we have at least one authentication method enabled
if len(conf.AuthMethods) == 0 {
if conf.Credentials != nil {
conf.AuthMethods = []Authenticator{&UserPassAuthenticator{conf.Credentials}}
} else {
conf.AuthMethods = []Authenticator{&NoAuthAuthenticator{}}
}
}
// Ensure we have a DNS resolver
if conf.Resolver == nil {
conf.Resolver = DNSResolver{}
}
// Ensure we have a rule set
if conf.Rules == nil {
conf.Rules = PermitAll()
}
// Ensure we have a log target
if conf.Logger == nil {
conf.Logger = log.New(os.Stdout, "", log.LstdFlags)
}
server := &Server{
config: conf,
}
server.authMethods = make(map[uint8]Authenticator)
for _, a := range conf.AuthMethods {
server.authMethods[a.GetCode()] = a
}
return server, nil
}
// ListenAndServe is used to create a listener and serve on it
func (s *Server) ListenAndServe(network, addr string) error {
l, err := net.Listen(network, addr)
if err != nil {
return err
}
return s.Serve(l)
}
// Serve is used to serve connections from a listener
func (s *Server) Serve(l net.Listener) error {
for {
conn, err := l.Accept()
if err != nil {
return err
}
go s.ServeConn(conn)
}
return nil
}
// ServeConn is used to serve a single connection.
func (s *Server) ServeConn(conn net.Conn) error {
defer conn.Close()
bufConn := bufio.NewReader(conn)
// Read the version byte
version := []byte{0}
if _, err := bufConn.Read(version); err != nil {
s.config.Logger.Printf("[ERR] socks: Failed to get version byte: %v", err)
return err
}
// Ensure we are compatible
if version[0] != socks5Version {
err := fmt.Errorf("Unsupported SOCKS version: %v", version)
s.config.Logger.Printf("[ERR] socks: %v", err)
return err
}
// Authenticate the connection
authContext, err := s.authenticate(conn, bufConn)
if err != nil {
err = fmt.Errorf("Failed to authenticate: %v", err)
s.config.Logger.Printf("[ERR] socks: %v", err)
return err
}
request, err := NewRequest(bufConn)
if err != nil {
if err == unrecognizedAddrType {
if err := sendReply(conn, addrTypeNotSupported, nil); err != nil {
return fmt.Errorf("Failed to send reply: %v", err)
}
}
return fmt.Errorf("Failed to read destination address: %v", err)
}
request.AuthContext = authContext
if client, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
request.RemoteAddr = &AddrSpec{IP: client.IP, Port: client.Port}
}
// Process the client request
if err := s.handleRequest(request, conn); err != nil {
err = fmt.Errorf("Failed to handle request: %v", err)
s.config.Logger.Printf("[ERR] socks: %v", err)
return err
}
return nil
}

156
vendor/golang.org/x/net/context/context.go generated vendored

@ -0,0 +1,156 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package context defines the Context type, which carries deadlines,
// cancelation signals, and other request-scoped values across API boundaries
// and between processes.
//
// Incoming requests to a server should create a Context, and outgoing calls to
// servers should accept a Context. The chain of function calls between must
// propagate the Context, optionally replacing it with a modified copy created
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
//
// Programs that use Contexts should follow these rules to keep interfaces
// consistent across packages and enable static analysis tools to check context
// propagation:
//
// Do not store Contexts inside a struct type; instead, pass a Context
// explicitly to each function that needs it. The Context should be the first
// parameter, typically named ctx:
//
// func DoSomething(ctx context.Context, arg Arg) error {
// // ... use ctx ...
// }
//
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
// if you are unsure about which Context to use.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The same Context may be passed to functions running in different goroutines;
// Contexts are safe for simultaneous use by multiple goroutines.
//
// See http://blog.golang.org/context for example code for a server that uses
// Contexts.
package context
import "time"
// A Context carries a deadline, a cancelation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See http://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancelation.
Done() <-chan struct{}
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
Err() error
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stores using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "golang.org/x/net/context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key = 0
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
}
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter). TODO is recognized by static analysis tools that determine
// whether Contexts are propagated correctly in a program.
func TODO() Context {
return todo
}
// A CancelFunc tells an operation to abandon its work.
// A CancelFunc does not wait for the work to stop.
// After the first call, subsequent calls to a CancelFunc do nothing.
type CancelFunc func()

72
vendor/golang.org/x/net/context/go17.go generated vendored

@ -0,0 +1,72 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.7
package context
import (
"context" // standard library's context, as of Go 1.7
"time"
)
var (
todo = context.TODO()
background = context.Background()
)
// Canceled is the error returned by Context.Err when the context is canceled.
var Canceled = context.Canceled
// DeadlineExceeded is the error returned by Context.Err when the context's
// deadline passes.
var DeadlineExceeded = context.DeadlineExceeded
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
ctx, f := context.WithCancel(parent)
return ctx, CancelFunc(f)
}
// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
ctx, f := context.WithDeadline(parent, deadline)
return ctx, CancelFunc(f)
}
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
// defer cancel() // releases resources if slowOperation completes before timeout elapses
// return slowOperation(ctx)
// }
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
func WithValue(parent Context, key interface{}, val interface{}) Context {
return context.WithValue(parent, key, val)
}

300
vendor/golang.org/x/net/context/pre_go17.go generated vendored

@ -0,0 +1,300 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.7
package context
import (
"errors"
"fmt"
"sync"
"time"
)
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Canceled is the error returned by Context.Err when the context is canceled.
var Canceled = errors.New("context canceled")
// DeadlineExceeded is the error returned by Context.Err when the context's
// deadline passes.
var DeadlineExceeded = errors.New("context deadline exceeded")
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, c)
return c, func() { c.cancel(true, Canceled) }
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) *cancelCtx {
return &cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]bool)
}
p.children[child] = true
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// parentCancelCtx follows a chain of parent references until it finds a
// *cancelCtx. This function understands how each of the concrete types in this
// package represents its parent.
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
func (c *cancelCtx) Done() <-chan struct{} {
return c.done
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
}
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
close(c.done)
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c)
d := deadline.Sub(time.Now())
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
*cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
// defer cancel() // releases resources if slowOperation completes before timeout elapses
// return slowOperation(ctx)
// }
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
func WithValue(parent Context, key interface{}, val interface{}) Context {
return &valueCtx{parent, key, val}
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
Loading…
Cancel
Save