mirror of
https://github.com/fatedier/frp.git
synced 2026-03-20 08:49:16 +08:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
134a46c00b | ||
|
|
50796643fb | ||
|
|
b1838b1d5e | ||
|
|
757b3613fe | ||
|
|
ae08811636 | ||
|
|
b657c0fe09 | ||
|
|
84df71047c | ||
|
|
abc6d720d0 | ||
|
|
80154639e3 | ||
|
|
f2117d8331 | ||
|
|
261be6a7b7 | ||
|
|
b53a2c1ed9 | ||
|
|
ee0df07a3c | ||
|
|
4e363eca2b | ||
|
|
4277405c0e | ||
|
|
6a99f0caf7 | ||
|
|
394af08561 | ||
|
|
6451583e60 | ||
|
|
30cb0a3ab0 | ||
|
|
5680a88267 | ||
|
|
6b089858db | ||
|
|
b3ed863021 | ||
|
|
5796c27ed5 | ||
|
|
310e8dd768 | ||
|
|
0b40ac2dbc | ||
|
|
f22c8e0882 | ||
|
|
a388bb2c95 | ||
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 | ||
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -1,5 +1,7 @@
|
|||||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||||
|
(请不要在 issue 评论中出现无意义的 **加1**,**我也是** 等内容,将会被直接删除。)
|
||||||
|
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
|
||||||
|
|
||||||
Use the commands below to provide key information from your environment:
|
Use the commands below to provide key information from your environment:
|
||||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ sudo: false
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -22,6 +22,7 @@ Now it also try to support p2p connect.
|
|||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||||
|
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||||
* [Expose your service in security](#expose-your-service-in-security)
|
* [Expose your service in security](#expose-your-service-in-security)
|
||||||
* [P2P Mode](#p2p-mode)
|
* [P2P Mode](#p2p-mode)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
@@ -44,6 +45,8 @@ Now it also try to support p2p connect.
|
|||||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||||
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
||||||
* [Get Real IP](#get-real-ip)
|
* [Get Real IP](#get-real-ip)
|
||||||
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||||
* [Custom subdomain names](#custom-subdomain-names)
|
* [Custom subdomain names](#custom-subdomain-names)
|
||||||
* [URL routing](#url-routing)
|
* [URL routing](#url-routing)
|
||||||
@@ -243,11 +246,34 @@ Configure frps same as above.
|
|||||||
|
|
||||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||||
|
|
||||||
|
### Enable HTTPS for local HTTP service
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_htts2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Visit `https://test.yourdomain.com`.
|
||||||
|
|
||||||
### Expose your service in security
|
### Expose your service in security
|
||||||
|
|
||||||
For some services, if expose them to the public network directly will be a security risk.
|
For some services, if expose them to the public network directly will be a security risk.
|
||||||
|
|
||||||
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
||||||
|
|
||||||
Configure frps same as above.
|
Configure frps same as above.
|
||||||
|
|
||||||
@@ -484,8 +510,6 @@ tcp_mux = false
|
|||||||
|
|
||||||
### Support KCP Protocol
|
### Support KCP Protocol
|
||||||
|
|
||||||
frp support kcp protocol since v0.12.0.
|
|
||||||
|
|
||||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||||
|
|
||||||
Using kcp in frp:
|
Using kcp in frp:
|
||||||
@@ -536,7 +560,8 @@ This feature is fit for a large number of short connections.
|
|||||||
### Load balancing
|
### Load balancing
|
||||||
|
|
||||||
Load balancing is supported by `group`.
|
Load balancing is supported by `group`.
|
||||||
This feature is available only for type `tcp` now.
|
|
||||||
|
This feature is available only for type `tcp` and `http` now.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -559,6 +584,10 @@ group_key = 123
|
|||||||
|
|
||||||
Proxies in same group will accept connections from port 80 randomly.
|
Proxies in same group will accept connections from port 80 randomly.
|
||||||
|
|
||||||
|
For `tcp` type, `remote_port` in one group shoud be same.
|
||||||
|
|
||||||
|
For `http` type, `custom_domains, subdomain, locations` shoud be same.
|
||||||
|
|
||||||
### Health Check
|
### Health Check
|
||||||
|
|
||||||
Health check feature can help you achieve high availability with load balancing.
|
Health check feature can help you achieve high availability with load balancing.
|
||||||
@@ -639,9 +668,32 @@ In this example, it will set header `X-From-Where: frp` to http request.
|
|||||||
|
|
||||||
### Get Real IP
|
### Get Real IP
|
||||||
|
|
||||||
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
Features for http proxy only.
|
Features for http proxy only.
|
||||||
|
|
||||||
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
||||||
|
|
||||||
|
#### Proxy Protocol
|
||||||
|
|
||||||
|
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
||||||
|
|
||||||
|
Here is an example for https service:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = https
|
||||||
|
local_port = 443
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
# now v1 and v2 is supported
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
```
|
||||||
|
|
||||||
|
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
||||||
|
|
||||||
|
Then you can get it from HTTP request header in your local service.
|
||||||
|
|
||||||
### Password protecting your web service
|
### Password protecting your web service
|
||||||
|
|
||||||
|
|||||||
72
README_zh.md
72
README_zh.md
@@ -16,8 +16,9 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
* [转发 Unix 域套接字](#转发-unix-域套接字)
|
||||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||||
|
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
* [点对点内网穿透](#点对点内网穿透)
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
* [功能说明](#功能说明)
|
* [功能说明](#功能说明)
|
||||||
@@ -40,6 +41,8 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [修改 Host Header](#修改-host-header)
|
* [修改 Host Header](#修改-host-header)
|
||||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||||
* [获取用户真实 IP](#获取用户真实-ip)
|
* [获取用户真实 IP](#获取用户真实-ip)
|
||||||
|
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||||
|
* [Proxy Protocol](#proxy-protocol)
|
||||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||||
* [自定义二级域名](#自定义二级域名)
|
* [自定义二级域名](#自定义二级域名)
|
||||||
* [URL 路由](#url-路由)
|
* [URL 路由](#url-路由)
|
||||||
@@ -191,7 +194,7 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
|||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.google.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
### 转发 Unix域套接字
|
### 转发 Unix 域套接字
|
||||||
|
|
||||||
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。
|
||||||
|
|
||||||
@@ -244,6 +247,33 @@ frps 的部署步骤同上。
|
|||||||
|
|
||||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||||
|
|
||||||
|
### 为本地 HTTP 服务启用 HTTPS
|
||||||
|
|
||||||
|
通过 `https2http` 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
|
||||||
|
|
||||||
|
1. 启用 frpc,启用 `https2http` 插件,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[test_htts2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
|
||||||
|
# HTTPS 证书相关的配置
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||||
|
|
||||||
### 安全地暴露内网服务
|
### 安全地暴露内网服务
|
||||||
|
|
||||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||||
@@ -514,7 +544,7 @@ tcp_mux = false
|
|||||||
|
|
||||||
### 底层通信可选 kcp 协议
|
### 底层通信可选 kcp 协议
|
||||||
|
|
||||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||||
|
|
||||||
开启 kcp 协议支持:
|
开启 kcp 协议支持:
|
||||||
|
|
||||||
@@ -566,7 +596,8 @@ tcp_mux = false
|
|||||||
### 负载均衡
|
### 负载均衡
|
||||||
|
|
||||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||||
目前只支持 tcp 类型的 proxy。
|
|
||||||
|
目前只支持 TCP 和 HTTP 类型的 proxy。
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
@@ -587,7 +618,9 @@ group_key = 123
|
|||||||
|
|
||||||
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。
|
||||||
|
|
||||||
要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
TCP 类型代理要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。
|
||||||
|
|
||||||
|
HTTP 类型代理要求 `group_key, custom_domains 或 subdomain 和 locations` 相同。
|
||||||
|
|
||||||
### 健康检查
|
### 健康检查
|
||||||
|
|
||||||
@@ -668,7 +701,34 @@ header_X-From-Where = frp
|
|||||||
|
|
||||||
### 获取用户真实 IP
|
### 获取用户真实 IP
|
||||||
|
|
||||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
|
#### HTTP X-Forwarded-For
|
||||||
|
|
||||||
|
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP,默认启用。
|
||||||
|
|
||||||
|
#### Proxy Protocol
|
||||||
|
|
||||||
|
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
|
||||||
|
|
||||||
|
**Proxy Protocol** 功能启用后,frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
|
||||||
|
|
||||||
|
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
|
||||||
|
|
||||||
|
这里以 https 类型为例:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[web]
|
||||||
|
type = https
|
||||||
|
local_port = 443
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
|
||||||
|
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
```
|
||||||
|
|
||||||
|
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
|
||||||
|
|
||||||
|
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
|
||||||
|
|
||||||
### 通过密码保护你的 web 服务
|
### 通过密码保护你的 web 服务
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
|||||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||||
|
|
||||||
// dispatch this work connection to related proxy
|
// dispatch this work connection to related proxy
|
||||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
|
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||||
@@ -148,6 +148,9 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
|||||||
func (ctl *Control) Close() error {
|
func (ctl *Control) Close() error {
|
||||||
ctl.pm.Close()
|
ctl.pm.Close()
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +205,7 @@ func (ctl *Control) reader() {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.Warn("read error: %v", err)
|
ctl.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -300,6 +304,9 @@ func (ctl *Control) worker() {
|
|||||||
ctl.vm.Close()
|
ctl.vm.Close()
|
||||||
|
|
||||||
close(ctl.closedDoneCh)
|
close(ctl.closedDoneCh)
|
||||||
|
if ctl.session != nil {
|
||||||
|
ctl.session.Close()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ func (monitor *HealthCheckMonitor) Stop() {
|
|||||||
|
|
||||||
func (monitor *HealthCheckMonitor) checkWorker() {
|
func (monitor *HealthCheckMonitor) checkWorker() {
|
||||||
for {
|
for {
|
||||||
ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
|
||||||
err := monitor.doCheck(ctx)
|
err := monitor.doCheck(doCtx)
|
||||||
|
|
||||||
// check if this monitor has been closed
|
// check if this monitor has been closed
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-monitor.ctx.Done():
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import (
|
|||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
pp "github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy defines how to handle work connections for different proxy type.
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
@@ -44,7 +45,7 @@ type Proxy interface {
|
|||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
// InWorkConn accept work connections registered to server.
|
// InWorkConn accept work connections registered to server.
|
||||||
InWorkConn(conn frpNet.Conn)
|
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
||||||
|
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
log.Logger
|
||||||
@@ -119,9 +120,9 @@ func (pxy *TcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
@@ -148,9 +149,9 @@ func (pxy *HttpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
@@ -177,9 +178,9 @@ func (pxy *HttpsProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
@@ -206,9 +207,9 @@ func (pxy *StcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
[]byte(g.GlbClientCfg.Token))
|
[]byte(g.GlbClientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
// XTCP
|
||||||
@@ -235,7 +236,7 @@ func (pxy *XtcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
var natHoleSidMsg msg.NatHoleSid
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
@@ -353,7 +354,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk))
|
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||||
@@ -415,7 +416,7 @@ func (pxy *UdpProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
// close resources releated with old workConn
|
// close resources releated with old workConn
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
@@ -482,7 +483,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
@@ -502,10 +503,43 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
remote = frpIo.WithCompression(remote)
|
remote = frpIo.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we need to send proxy protocol info
|
||||||
|
var extraInfo []byte
|
||||||
|
if baseInfo.ProxyProtocolVersion != "" {
|
||||||
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
|
if m.DstAddr == "" {
|
||||||
|
m.DstAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
h := &pp.Header{
|
||||||
|
Command: pp.PROXY,
|
||||||
|
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||||
|
SourcePort: m.SrcPort,
|
||||||
|
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||||
|
DestinationPort: m.DstPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.SourceAddress.To16() == nil {
|
||||||
|
h.TransportProtocol = pp.TCPv4
|
||||||
|
} else {
|
||||||
|
h.TransportProtocol = pp.TCPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||||
|
h.Version = 1
|
||||||
|
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||||
|
h.Version = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
h.WriteTo(buf)
|
||||||
|
extraInfo = buf.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if proxyPlugin != nil {
|
if proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connections first
|
// if plugin is set, let plugin handle connections first
|
||||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||||
proxyPlugin.Handle(remote, workConn)
|
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||||
workConn.Debug("handle by plugin finished")
|
workConn.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -518,6 +552,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
|
|
||||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
if len(extraInfo) > 0 {
|
||||||
|
localConn.Write(extraInfo)
|
||||||
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, remote)
|
frpIo.Join(localConn, remote)
|
||||||
workConn.Debug("join connections closed")
|
workConn.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ func (pm *ProxyManager) Close() {
|
|||||||
pm.proxies = make(map[string]*ProxyWrapper)
|
pm.proxies = make(map[string]*ProxyWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
|
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
pw, ok := pm.proxies[name]
|
pw, ok := pm.proxies[name]
|
||||||
pm.mu.RUnlock()
|
pm.mu.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
pw.InWorkConn(workConn)
|
pw.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,13 +217,13 @@ func (pw *ProxyWrapper) statusFailedCallback() {
|
|||||||
pw.Info("health check failed")
|
pw.Info("health check failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
|
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||||
pw.mu.RLock()
|
pw.mu.RLock()
|
||||||
pxy := pw.pxy
|
pxy := pw.pxy
|
||||||
pw.mu.RUnlock()
|
pw.mu.RUnlock()
|
||||||
if pxy != nil {
|
if pxy != nil {
|
||||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
go pxy.InWorkConn(workConn)
|
go pxy.InWorkConn(workConn, m)
|
||||||
} else {
|
} else {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
if session != nil {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -76,17 +76,16 @@ func reload() error {
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
if resp.StatusCode == 200 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
||||||
}
|
}
|
||||||
return nil
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
kcpDoneCh = make(chan struct{})
|
kcpDoneCh = make(chan struct{})
|
||||||
|
|||||||
@@ -78,76 +78,78 @@ func status() error {
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res := &client.StatusResp{}
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Proxy Status...")
|
|
||||||
if len(res.Tcp) > 0 {
|
|
||||||
fmt.Printf("TCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Tcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Udp) > 0 {
|
|
||||||
fmt.Printf("UDP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Udp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Http) > 0 {
|
|
||||||
fmt.Printf("HTTP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Http {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Https) > 0 {
|
|
||||||
fmt.Printf("HTTPS")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Https {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Stcp) > 0 {
|
|
||||||
fmt.Printf("STCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Stcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
if len(res.Xtcp) > 0 {
|
|
||||||
fmt.Printf("XTCP")
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range res.Xtcp {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res := &client.StatusResp{}
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Proxy Status...")
|
||||||
|
if len(res.Tcp) > 0 {
|
||||||
|
fmt.Printf("TCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Tcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Udp) > 0 {
|
||||||
|
fmt.Printf("UDP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Udp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Http) > 0 {
|
||||||
|
fmt.Printf("HTTP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Http {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Https) > 0 {
|
||||||
|
fmt.Printf("HTTPS")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Https {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Stcp) > 0 {
|
||||||
|
fmt.Printf("STCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Stcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
if len(res.Xtcp) > 0 {
|
||||||
|
fmt.Printf("XTCP")
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range res.Xtcp {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||||
@@ -79,7 +79,7 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days")
|
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
||||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ tls_enable = true
|
|||||||
# specify a dns server, so frpc will use this instead of default one
|
# specify a dns server, so frpc will use this instead of default one
|
||||||
# dns_server = 8.8.8.8
|
# dns_server = 8.8.8.8
|
||||||
|
|
||||||
# proxy names you want to start divided by ','
|
# proxy names you want to start seperated by ','
|
||||||
# default is empty, means all proxies
|
# default is empty, means all proxies
|
||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
|
|
||||||
@@ -154,6 +154,9 @@ use_encryption = false
|
|||||||
use_compression = false
|
use_compression = false
|
||||||
subdomain = web01
|
subdomain = web01
|
||||||
custom_domains = web02.yourdomain.com
|
custom_domains = web02.yourdomain.com
|
||||||
|
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||||
|
# v1 or v2 or empty
|
||||||
|
proxy_protocol_version = v2
|
||||||
|
|
||||||
[plugin_unix_domain_socket]
|
[plugin_unix_domain_socket]
|
||||||
type = tcp
|
type = tcp
|
||||||
@@ -187,6 +190,15 @@ plugin_strip_prefix = static
|
|||||||
plugin_http_user = abc
|
plugin_http_user = abc
|
||||||
plugin_http_passwd = abc
|
plugin_http_passwd = abc
|
||||||
|
|
||||||
|
[plugin_https2http]
|
||||||
|
type = https
|
||||||
|
custom_domains = test.yourdomain.com
|
||||||
|
plugin = https2http
|
||||||
|
plugin_local_addr = 127.0.0.1:80
|
||||||
|
plugin_crt_path = ./server.crt
|
||||||
|
plugin_key_path = ./server.key
|
||||||
|
plugin_host_header_rewrite = 127.0.0.1
|
||||||
|
|
||||||
[secret_tcp]
|
[secret_tcp]
|
||||||
# If the type is secret tcp, remote_port is useless
|
# If the type is secret tcp, remote_port is useless
|
||||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
|
|||||||
@@ -65,3 +65,6 @@ subdomain_host = frps.com
|
|||||||
|
|
||||||
# if tcp stream multiplexing is used, default is true
|
# if tcp stream multiplexing is used, default is true
|
||||||
tcp_mux = true
|
tcp_mux = true
|
||||||
|
|
||||||
|
# custom 404 page for HTTP requests
|
||||||
|
# custom_404_page = /path/to/404.html
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -4,30 +4,29 @@ go 1.12
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/gorilla/mux v1.6.2
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/gorilla/websocket v1.2.0
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rakyll/statik v0.1.1
|
github.com/rakyll/statik v0.1.1
|
||||||
github.com/rodaine/table v1.0.0
|
github.com/rodaine/table v1.0.0
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
github.com/spf13/pflag v1.0.1 // indirect
|
github.com/spf13/pflag v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.2.1
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
44
go.sum
44
go.sum
@@ -1,15 +1,18 @@
|
|||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
@@ -20,16 +23,39 @@ github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4
|
|||||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||||
|
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||||
|
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||||
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||||
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -107,8 +107,10 @@ type BaseProxyConf struct {
|
|||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
GroupKey string `json:"group_key"`
|
GroupKey string `json:"group_key"`
|
||||||
|
|
||||||
|
// only used for client
|
||||||
|
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||||
LocalSvrConf
|
LocalSvrConf
|
||||||
HealthCheckConf // only used for client
|
HealthCheckConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||||
@@ -121,7 +123,8 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
|||||||
cfg.UseEncryption != cmp.UseEncryption ||
|
cfg.UseEncryption != cmp.UseEncryption ||
|
||||||
cfg.UseCompression != cmp.UseCompression ||
|
cfg.UseCompression != cmp.UseCompression ||
|
||||||
cfg.Group != cmp.Group ||
|
cfg.Group != cmp.Group ||
|
||||||
cfg.GroupKey != cmp.GroupKey {
|
cfg.GroupKey != cmp.GroupKey ||
|
||||||
|
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||||
@@ -162,6 +165,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
|||||||
|
|
||||||
cfg.Group = section["group"]
|
cfg.Group = section["group"]
|
||||||
cfg.GroupKey = section["group_key"]
|
cfg.GroupKey = section["group_key"]
|
||||||
|
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||||
|
|
||||||
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -194,6 +198,12 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||||
|
if cfg.ProxyProtocolVersion != "" {
|
||||||
|
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||||
|
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ type ServerCommonConf struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
SubDomainHost string `json:"subdomain_host"`
|
SubDomainHost string `json:"subdomain_host"`
|
||||||
TcpMux bool `json:"tcp_mux"`
|
TcpMux bool `json:"tcp_mux"`
|
||||||
|
Custom404Page string `json:"custom_404_page"`
|
||||||
|
|
||||||
AllowPorts map[int]struct{}
|
AllowPorts map[int]struct{}
|
||||||
MaxPoolCount int64 `json:"max_pool_count"`
|
MaxPoolCount int64 `json:"max_pool_count"`
|
||||||
@@ -104,6 +105,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
|||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
|
Custom404Page: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +295,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
|||||||
cfg.TcpMux = true
|
cfg.TcpMux = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
|
||||||
|
cfg.Custom404Page = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ type ReqWorkConn struct {
|
|||||||
|
|
||||||
type StartWorkConn struct {
|
type StartWorkConn struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SrcAddr string `json:"src_addr"`
|
||||||
|
DstAddr string `json:"dst_addr"`
|
||||||
|
SrcPort uint16 `json:"src_port"`
|
||||||
|
DstPort uint16 `json:"dst_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewVisitorConn struct {
|
type NewVisitorConn struct {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
|
|||||||
return PluginHttpProxy
|
return PluginHttpProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||||
|
|||||||
114
models/plugin/https2http.go
Normal file
114
models/plugin/https2http.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginHTTPS2HTTP = "https2http"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPS2HTTPPlugin struct {
|
||||||
|
crtPath string
|
||||||
|
keyPath string
|
||||||
|
hostHeaderRewrite string
|
||||||
|
localAddr string
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||||
|
crtPath := params["plugin_crt_path"]
|
||||||
|
keyPath := params["plugin_key_path"]
|
||||||
|
localAddr := params["plugin_local_addr"]
|
||||||
|
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||||
|
|
||||||
|
if crtPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||||
|
}
|
||||||
|
if keyPath == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_key_path is required")
|
||||||
|
}
|
||||||
|
if localAddr == "" {
|
||||||
|
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
p := &HTTPS2HTTPPlugin{
|
||||||
|
crtPath: crtPath,
|
||||||
|
keyPath: keyPath,
|
||||||
|
localAddr: localAddr,
|
||||||
|
hostHeaderRewrite: hostHeaderRewrite,
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = p.localAddr
|
||||||
|
if p.hostHeaderRewrite != "" {
|
||||||
|
req.Host = p.hostHeaderRewrite
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: rp,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := p.genTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
|
}
|
||||||
|
ln := tls.NewListener(listener, tlsConfig)
|
||||||
|
|
||||||
|
go p.s.Serve(ln)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||||
|
return PluginHTTPS2HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
|||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.Server.ServeConn(wrapConn)
|
sp.Server.ServeConn(wrapConn)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
sp.l.PutConn(wrapConn)
|
sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(extraBufToLocal) > 0 {
|
||||||
|
localConn.Write(extraBufToLocal)
|
||||||
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, conn)
|
frpIo.Join(localConn, conn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ func (ctl *Control) reader() {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.conn.Warn("read error: %v", err)
|
ctl.conn.Warn("read error: %v", err)
|
||||||
|
ctl.conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ type ResourceController struct {
|
|||||||
// Tcp Group Controller
|
// Tcp Group Controller
|
||||||
TcpGroupCtl *group.TcpGroupCtl
|
TcpGroupCtl *group.TcpGroupCtl
|
||||||
|
|
||||||
|
// HTTP Group Controller
|
||||||
|
HTTPGroupCtl *group.HTTPGroupController
|
||||||
|
|
||||||
// Manage all tcp ports
|
// Manage all tcp ports
|
||||||
TcpPortManager *ports.PortManager
|
TcpPortManager *ports.PortManager
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ type ResourceController struct {
|
|||||||
// For http proxies, forwarding http requests
|
// For http proxies, forwarding http requests
|
||||||
HttpReverseProxy *vhost.HttpReverseProxy
|
HttpReverseProxy *vhost.HttpReverseProxy
|
||||||
|
|
||||||
// For https proxies, route requests to different clients by hostname and other infomation
|
// For https proxies, route requests to different clients by hostname and other information
|
||||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||||
|
|
||||||
// Controller for nat hole connections
|
// Controller for nat hole connections
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
|||||||
proxyInfo.CurConns = ps.CurConns
|
proxyInfo.CurConns = ps.CurConns
|
||||||
proxyInfo.LastStartTime = ps.LastStartTime
|
proxyInfo.LastStartTime = ps.LastStartTime
|
||||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||||
|
code = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ var (
|
|||||||
ErrGroupParamsInvalid = errors.New("group params invalid")
|
ErrGroupParamsInvalid = errors.New("group params invalid")
|
||||||
ErrListenerClosed = errors.New("group listener closed")
|
ErrListenerClosed = errors.New("group listener closed")
|
||||||
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
ErrGroupDifferentPort = errors.New("group should have same remote port")
|
||||||
|
ErrProxyRepeated = errors.New("group proxy repeated")
|
||||||
)
|
)
|
||||||
|
|||||||
157
server/group/http.go
Normal file
157
server/group/http.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPGroupController struct {
|
||||||
|
groups map[string]*HTTPGroup
|
||||||
|
|
||||||
|
vhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupController {
|
||||||
|
return &HTTPGroupController{
|
||||||
|
groups: make(map[string]*HTTPGroup),
|
||||||
|
vhostRouter: vhostRouter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
indexKey := httpGroupIndex(group, routeConfig.Domain, routeConfig.Location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
g = NewHTTPGroup(ctl)
|
||||||
|
ctl.groups[indexKey] = g
|
||||||
|
}
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
|
return g.Register(proxyName, group, groupKey, routeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *HTTPGroupController) UnRegister(proxyName, group, domain, location string) {
|
||||||
|
indexKey := httpGroupIndex(group, domain, location)
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
g, ok := ctl.groups[indexKey]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty := g.UnRegister(proxyName)
|
||||||
|
if isEmpty {
|
||||||
|
delete(ctl.groups, indexKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPGroup struct {
|
||||||
|
group string
|
||||||
|
groupKey string
|
||||||
|
domain string
|
||||||
|
location string
|
||||||
|
|
||||||
|
createFuncs map[string]vhost.CreateConnFunc
|
||||||
|
pxyNames []string
|
||||||
|
index uint64
|
||||||
|
ctl *HTTPGroupController
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {
|
||||||
|
return &HTTPGroup{
|
||||||
|
createFuncs: make(map[string]vhost.CreateConnFunc),
|
||||||
|
pxyNames: make([]string, 0),
|
||||||
|
ctl: ctl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) Register(proxyName, group, groupKey string,
|
||||||
|
routeConfig vhost.VhostRouteConfig) (err error) {
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
// the first proxy in this group
|
||||||
|
tmp := routeConfig // copy object
|
||||||
|
tmp.CreateConnFn = g.createConn
|
||||||
|
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.group = group
|
||||||
|
g.groupKey = groupKey
|
||||||
|
g.domain = routeConfig.Domain
|
||||||
|
g.location = routeConfig.Location
|
||||||
|
} else {
|
||||||
|
if g.group != group || g.domain != routeConfig.Domain || g.location != routeConfig.Location {
|
||||||
|
err = ErrGroupParamsInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if g.groupKey != groupKey {
|
||||||
|
err = ErrGroupAuthFailed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := g.createFuncs[proxyName]; ok {
|
||||||
|
err = ErrProxyRepeated
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.createFuncs[proxyName] = routeConfig.CreateConnFn
|
||||||
|
g.pxyNames = append(g.pxyNames, proxyName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
delete(g.createFuncs, proxyName)
|
||||||
|
for i, name := range g.pxyNames {
|
||||||
|
if name == proxyName {
|
||||||
|
g.pxyNames = append(g.pxyNames[:i], g.pxyNames[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.createFuncs) == 0 {
|
||||||
|
isEmpty = true
|
||||||
|
g.ctl.vhostRouter.Del(g.domain, g.location)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
|
||||||
|
var f vhost.CreateConnFunc
|
||||||
|
newIndex := atomic.AddUint64(&g.index, 1)
|
||||||
|
|
||||||
|
g.mu.RLock()
|
||||||
|
group := g.group
|
||||||
|
domain := g.domain
|
||||||
|
location := g.location
|
||||||
|
if len(g.pxyNames) > 0 {
|
||||||
|
name := g.pxyNames[int(newIndex)%len(g.pxyNames)]
|
||||||
|
f, _ = g.createFuncs[name]
|
||||||
|
}
|
||||||
|
g.mu.RUnlock()
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
return nil, fmt.Errorf("no CreateConnFunc for http group [%s], domain [%s], location [%s]", group, domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpGroupIndex(group, domain, location string) string {
|
||||||
|
return fmt.Sprintf("%s_%s_%s", group, domain, location)
|
||||||
|
}
|
||||||
@@ -24,46 +24,47 @@ import (
|
|||||||
gerr "github.com/fatedier/golib/errors"
|
gerr "github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TcpGroupListener struct {
|
// TcpGroupCtl manage all TcpGroups
|
||||||
groupName string
|
type TcpGroupCtl struct {
|
||||||
group *TcpGroup
|
groups map[string]*TcpGroup
|
||||||
|
|
||||||
addr net.Addr
|
// portManager is used to manage port
|
||||||
closeCh chan struct{}
|
portManager *ports.PortManager
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
// NewTcpGroupCtl return a new TcpGroupCtl
|
||||||
return &TcpGroupListener{
|
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
||||||
groupName: name,
|
return &TcpGroupCtl{
|
||||||
group: group,
|
groups: make(map[string]*TcpGroup),
|
||||||
addr: addr,
|
portManager: portManager,
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
// Listen is the wrapper for TcpGroup's Listen
|
||||||
var ok bool
|
// If there are no group, we will create one here
|
||||||
select {
|
func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
|
||||||
case <-ln.closeCh:
|
addr string, port int) (l net.Listener, realPort int, err error) {
|
||||||
return nil, ErrListenerClosed
|
|
||||||
case c, ok = <-ln.group.Accept():
|
tgc.mu.Lock()
|
||||||
if !ok {
|
tcpGroup, ok := tgc.groups[group]
|
||||||
return nil, ErrListenerClosed
|
if !ok {
|
||||||
}
|
tcpGroup = NewTcpGroup(tgc)
|
||||||
return c, nil
|
tgc.groups[group] = tcpGroup
|
||||||
}
|
}
|
||||||
|
tgc.mu.Unlock()
|
||||||
|
|
||||||
|
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ln *TcpGroupListener) Addr() net.Addr {
|
// RemoveGroup remove TcpGroup from controller
|
||||||
return ln.addr
|
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
||||||
}
|
tgc.mu.Lock()
|
||||||
|
defer tgc.mu.Unlock()
|
||||||
func (ln *TcpGroupListener) Close() (err error) {
|
delete(tgc.groups, group)
|
||||||
close(ln.closeCh)
|
|
||||||
ln.group.CloseListener(ln)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TcpGroup route connections to different proxies
|
||||||
type TcpGroup struct {
|
type TcpGroup struct {
|
||||||
group string
|
group string
|
||||||
groupKey string
|
groupKey string
|
||||||
@@ -79,6 +80,7 @@ type TcpGroup struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTcpGroup return a new TcpGroup
|
||||||
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
||||||
return &TcpGroup{
|
return &TcpGroup{
|
||||||
lns: make([]*TcpGroupListener, 0),
|
lns: make([]*TcpGroupListener, 0),
|
||||||
@@ -87,10 +89,14 @@ func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen will return a new TcpGroupListener
|
||||||
|
// if TcpGroup already has a listener, just add a new TcpGroupListener to the queues
|
||||||
|
// otherwise, listen on the real address
|
||||||
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
|
||||||
tg.mu.Lock()
|
tg.mu.Lock()
|
||||||
defer tg.mu.Unlock()
|
defer tg.mu.Unlock()
|
||||||
if len(tg.lns) == 0 {
|
if len(tg.lns) == 0 {
|
||||||
|
// the first listener, listen on the real address
|
||||||
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
realPort, err = tg.ctl.portManager.Acquire(proxyName, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -114,6 +120,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
|||||||
}
|
}
|
||||||
go tg.worker()
|
go tg.worker()
|
||||||
} else {
|
} else {
|
||||||
|
// address and port in the same group must be equal
|
||||||
if tg.group != group || tg.addr != addr {
|
if tg.group != group || tg.addr != addr {
|
||||||
err = ErrGroupParamsInvalid
|
err = ErrGroupParamsInvalid
|
||||||
return
|
return
|
||||||
@@ -133,6 +140,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// worker is called when the real tcp listener has been created
|
||||||
func (tg *TcpGroup) worker() {
|
func (tg *TcpGroup) worker() {
|
||||||
for {
|
for {
|
||||||
c, err := tg.tcpLn.Accept()
|
c, err := tg.tcpLn.Accept()
|
||||||
@@ -152,6 +160,7 @@ func (tg *TcpGroup) Accept() <-chan net.Conn {
|
|||||||
return tg.acceptCh
|
return tg.acceptCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseListener remove the TcpGroupListener from the TcpGroup
|
||||||
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
||||||
tg.mu.Lock()
|
tg.mu.Lock()
|
||||||
defer tg.mu.Unlock()
|
defer tg.mu.Unlock()
|
||||||
@@ -169,36 +178,47 @@ func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TcpGroupCtl struct {
|
// TcpGroupListener
|
||||||
groups map[string]*TcpGroup
|
type TcpGroupListener struct {
|
||||||
|
groupName string
|
||||||
|
group *TcpGroup
|
||||||
|
|
||||||
portManager *ports.PortManager
|
addr net.Addr
|
||||||
mu sync.Mutex
|
closeCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
|
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
|
||||||
return &TcpGroupCtl{
|
return &TcpGroupListener{
|
||||||
groups: make(map[string]*TcpGroup),
|
groupName: name,
|
||||||
portManager: portManager,
|
group: group,
|
||||||
|
addr: addr,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string,
|
// Accept will accept connections from TcpGroup
|
||||||
addr string, port int) (l net.Listener, realPort int, err error) {
|
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
|
||||||
|
var ok bool
|
||||||
tgc.mu.Lock()
|
select {
|
||||||
defer tgc.mu.Unlock()
|
case <-ln.closeCh:
|
||||||
if tcpGroup, ok := tgc.groups[group]; ok {
|
return nil, ErrListenerClosed
|
||||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
case c, ok = <-ln.group.Accept():
|
||||||
} else {
|
if !ok {
|
||||||
tcpGroup = NewTcpGroup(tgc)
|
return nil, ErrListenerClosed
|
||||||
tgc.groups[group] = tcpGroup
|
}
|
||||||
return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port)
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
|
func (ln *TcpGroupListener) Addr() net.Addr {
|
||||||
tgc.mu.Lock()
|
return ln.addr
|
||||||
defer tgc.mu.Unlock()
|
}
|
||||||
delete(tgc.groups, group)
|
|
||||||
|
// Close close the listener
|
||||||
|
func (ln *TcpGroupListener) Close() (err error) {
|
||||||
|
close(ln.closeCh)
|
||||||
|
|
||||||
|
// remove self from TcpGroup
|
||||||
|
ln.group.CloseListener(ln)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package proxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
@@ -49,22 +50,46 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
locations = []string{""}
|
locations = []string{""}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
for _, domain := range pxy.cfg.CustomDomains {
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpDomain := routeConfig.Domain
|
tmpDomain := routeConfig.Domain
|
||||||
tmpLocation := routeConfig.Location
|
tmpLocation := routeConfig.Location
|
||||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort)))
|
|
||||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
// handle group
|
||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
if pxy.cfg.Group != "" {
|
||||||
})
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// no group
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpPort)))
|
||||||
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,17 +97,31 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpDomain := routeConfig.Domain
|
tmpDomain := routeConfig.Domain
|
||||||
tmpLocation := routeConfig.Location
|
tmpLocation := routeConfig.Location
|
||||||
|
|
||||||
|
// handle group
|
||||||
|
if pxy.cfg.Group != "" {
|
||||||
|
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
|
||||||
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
|
||||||
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
|
||||||
})
|
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remoteAddr = strings.Join(addrs, ",")
|
remoteAddr = strings.Join(addrs, ",")
|
||||||
@@ -93,8 +132,14 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
|||||||
return pxy.cfg
|
return pxy.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
||||||
tmpConn, errRet := pxy.GetWorkConnFromPool()
|
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||||
|
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = errRet
|
err = errRet
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,8 +31,17 @@ type HttpsProxy struct {
|
|||||||
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||||
routeConfig := &vhost.VhostRouteConfig{}
|
routeConfig := &vhost.VhostRouteConfig{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
for _, domain := range pxy.cfg.CustomDomains {
|
for _, domain := range pxy.cfg.CustomDomains {
|
||||||
|
if domain == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
@@ -36,7 +38,7 @@ type Proxy interface {
|
|||||||
Run() (remoteAddr string, err error)
|
Run() (remoteAddr string, err error)
|
||||||
GetName() string
|
GetName() string
|
||||||
GetConf() config.ProxyConf
|
GetConf() config.ProxyConf
|
||||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
||||||
GetUsedPortsNum() int
|
GetUsedPortsNum() int
|
||||||
Close()
|
Close()
|
||||||
log.Logger
|
log.Logger
|
||||||
@@ -70,7 +72,9 @@ func (pxy *BaseProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
// GetWorkConnFromPool try to get a new work connections from pool
|
||||||
|
// for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
|
||||||
|
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
for i := 0; i < pxy.poolCount+1; i++ {
|
for i := 0; i < pxy.poolCount+1; i++ {
|
||||||
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||||
@@ -80,8 +84,29 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
|||||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
workConn.AddLogPrefix(pxy.GetName())
|
workConn.AddLogPrefix(pxy.GetName())
|
||||||
|
|
||||||
|
var (
|
||||||
|
srcAddr string
|
||||||
|
dstAddr string
|
||||||
|
srcPortStr string
|
||||||
|
dstPortStr string
|
||||||
|
srcPort int
|
||||||
|
dstPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
if src != nil {
|
||||||
|
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||||
|
srcPort, _ = strconv.Atoi(srcPortStr)
|
||||||
|
}
|
||||||
|
if dst != nil {
|
||||||
|
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||||
|
dstPort, _ = strconv.Atoi(dstPortStr)
|
||||||
|
}
|
||||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
ProxyName: pxy.GetName(),
|
ProxyName: pxy.GetName(),
|
||||||
|
SrcAddr: srcAddr,
|
||||||
|
SrcPort: uint16(srcPort),
|
||||||
|
DstAddr: dstAddr,
|
||||||
|
DstPort: uint16(dstPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||||
@@ -177,7 +202,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
|
|||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
workConn, err := pxy.GetWorkConnFromPool()
|
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
|||||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
for {
|
for {
|
||||||
workConn, err := pxy.GetWorkConnFromPool()
|
workConn, err := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
// check if proxy is closed
|
// check if proxy is closed
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
break
|
break
|
||||||
case sidRequest := <-sidCh:
|
case sidRequest := <-sidCh:
|
||||||
sr := sidRequest
|
sr := sidRequest
|
||||||
workConn, errRet := pxy.GetWorkConnFromPool()
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ type Service struct {
|
|||||||
// Manage all proxies
|
// Manage all proxies
|
||||||
pxyManager *proxy.ProxyManager
|
pxyManager *proxy.ProxyManager
|
||||||
|
|
||||||
|
// HTTP vhost router
|
||||||
|
httpVhostRouter *vhost.VhostRouters
|
||||||
|
|
||||||
// All resource managers and controllers
|
// All resource managers and controllers
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
|
|
||||||
@@ -95,12 +98,16 @@ func NewService() (svr *Service, err error) {
|
|||||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||||
},
|
},
|
||||||
tlsConfig: generateTLSConfig(),
|
httpVhostRouter: vhost.NewVhostRouters(),
|
||||||
|
tlsConfig: generateTLSConfig(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init group controller
|
// Init group controller
|
||||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||||
|
|
||||||
|
// Init HTTP group controller
|
||||||
|
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
||||||
|
|
||||||
// Init assets
|
// Init assets
|
||||||
err = assets.Load(cfg.AssetsDir)
|
err = assets.Load(cfg.AssetsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,6 +115,9 @@ func NewService() (svr *Service, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init 404 not found page
|
||||||
|
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpMuxOn bool
|
httpMuxOn bool
|
||||||
httpsMuxOn bool
|
httpsMuxOn bool
|
||||||
@@ -156,7 +166,7 @@ func NewService() (svr *Service, err error) {
|
|||||||
if cfg.VhostHttpPort > 0 {
|
if cfg.VhostHttpPort > 0 {
|
||||||
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
|
||||||
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
|
||||||
})
|
}, svr.httpVhostRouter)
|
||||||
svr.rc.HttpReverseProxy = rp
|
svr.rc.HttpReverseProxy = rp
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
@@ -256,7 +266,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
|||||||
log.Warn("Listener for incoming connections from client closed")
|
log.Warn("Listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c = frpNet.CheckAndEnableTLSServerConn(c, svr.tlsConfig)
|
|
||||||
|
log.Trace("start check TLS connection...")
|
||||||
|
originConn := c
|
||||||
|
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
|
originConn.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Trace("success check TLS connection")
|
||||||
|
|
||||||
// Start a new goroutine for dealing connections.
|
// Start a new goroutine for dealing connections.
|
||||||
go func(frpConn frpNet.Conn) {
|
go func(frpConn frpNet.Conn) {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ health_check_url = /health
|
|||||||
func TestHealthCheck(t *testing.T) {
|
func TestHealthCheck(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// ****** start backgroud services ******
|
// ****** start background services ******
|
||||||
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
|
echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
|
||||||
err := echoSvc1.Start()
|
err := echoSvc1.Start()
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
|
|||||||
@@ -28,51 +28,51 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
} else {
|
}
|
||||||
if resp.StatusCode != 200 {
|
defer resp.Body.Close()
|
||||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
if resp.StatusCode != 200 {
|
||||||
}
|
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
defer resp.Body.Close()
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
allStatus := &client.StatusResp{}
|
allStatus := &client.StatusResp{}
|
||||||
err = json.Unmarshal(body, &allStatus)
|
err = json.Unmarshal(body, &allStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
for _, s := range allStatus.Tcp {
|
for _, s := range allStatus.Tcp {
|
||||||
if s.Name == name {
|
if s.Name == name {
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Udp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Http {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Https {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Stcp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, s := range allStatus.Xtcp {
|
|
||||||
if s.Name == name {
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, s := range allStatus.Udp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Http {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Https {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Stcp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range allStatus.Xtcp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return status, errors.New("no proxy status found")
|
return status, errors.New("no proxy status found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +87,13 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
|
|||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
io.Copy(ioutil.Discard, resp.Body)
|
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package net
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
gnet "github.com/fatedier/golib/net"
|
||||||
)
|
)
|
||||||
@@ -31,10 +32,17 @@ func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckAndEnableTLSServerConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
|
||||||
sc, r := gnet.NewSharedConnSize(c, 1)
|
sc, r := gnet.NewSharedConnSize(c, 2)
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
n, _ := r.Read(buf)
|
var n int
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
c.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||||
out = WrapConn(tls.Server(c, tlsConfig))
|
out = WrapConn(tls.Server(c, tlsConfig))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.25.3"
|
var version string = "0.28.2"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpLog "github.com/fatedier/frp/utils/log"
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
@@ -32,8 +31,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
ErrNoDomain = errors.New("no such domain")
|
||||||
ErrNoDomain = errors.New("no such domain")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHostFromAddr(addr string) (host string) {
|
func getHostFromAddr(addr string) (host string) {
|
||||||
@@ -51,21 +49,19 @@ type HttpReverseProxyOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HttpReverseProxy struct {
|
type HttpReverseProxy struct {
|
||||||
proxy *ReverseProxy
|
proxy *ReverseProxy
|
||||||
|
|
||||||
vhostRouter *VhostRouters
|
vhostRouter *VhostRouters
|
||||||
|
|
||||||
responseHeaderTimeout time.Duration
|
responseHeaderTimeout time.Duration
|
||||||
cfgMu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRouters) *HttpReverseProxy {
|
||||||
if option.ResponseHeaderTimeoutS <= 0 {
|
if option.ResponseHeaderTimeoutS <= 0 {
|
||||||
option.ResponseHeaderTimeoutS = 60
|
option.ResponseHeaderTimeoutS = 60
|
||||||
}
|
}
|
||||||
rp := &HttpReverseProxy{
|
rp := &HttpReverseProxy{
|
||||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||||
vhostRouter: NewVhostRouters(),
|
vhostRouter: vhostRouter,
|
||||||
}
|
}
|
||||||
proxy := &ReverseProxy{
|
proxy := &ReverseProxy{
|
||||||
Director: func(req *http.Request) {
|
Director: func(req *http.Request) {
|
||||||
@@ -89,36 +85,34 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
|||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
url := ctx.Value("url").(string)
|
url := ctx.Value("url").(string)
|
||||||
host := getHostFromAddr(ctx.Value("host").(string))
|
host := getHostFromAddr(ctx.Value("host").(string))
|
||||||
return rp.CreateConnection(host, url)
|
remote := ctx.Value("remote").(string)
|
||||||
|
return rp.CreateConnection(host, url, remote)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
url := ctx.Value("url").(string)
|
|
||||||
host := getHostFromAddr(ctx.Value("host").(string))
|
|
||||||
return rp.CreateConnection(host, url)
|
|
||||||
},
|
|
||||||
BufferPool: newWrapPool(),
|
BufferPool: newWrapPool(),
|
||||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
|
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
frpLog.Warn("do http proxy request error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
rw.Write(getNotFoundPageContent())
|
||||||
|
},
|
||||||
}
|
}
|
||||||
rp.proxy = proxy
|
rp.proxy = proxy
|
||||||
return rp
|
return rp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register register the route config to reverse proxy
|
||||||
|
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
|
||||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||||
rp.cfgMu.Lock()
|
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||||
defer rp.cfgMu.Unlock()
|
if err != nil {
|
||||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
return err
|
||||||
if ok {
|
|
||||||
return ErrRouterConfigConflict
|
|
||||||
} else {
|
|
||||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnRegister unregister route config by domain and location
|
||||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||||
rp.cfgMu.Lock()
|
|
||||||
defer rp.cfgMu.Unlock()
|
|
||||||
rp.vhostRouter.Del(domain, location)
|
rp.vhostRouter.Del(domain, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +132,13 @@ func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
|
// CreateConnection create a new connection by route config
|
||||||
|
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
|
||||||
vr, ok := rp.getVhost(domain, location)
|
vr, ok := rp.getVhost(domain, location)
|
||||||
if ok {
|
if ok {
|
||||||
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
return fn()
|
return fn(remoteAddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
||||||
@@ -161,10 +156,8 @@ func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) boo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getVhost get vhost router by domain and location
|
||||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||||
rp.cfgMu.RLock()
|
|
||||||
defer rp.cfgMu.RUnlock()
|
|
||||||
|
|
||||||
// first we check the full hostname
|
// first we check the full hostname
|
||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
@@ -15,13 +15,18 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
NotFoundPagePath = ""
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotFound = `<!DOCTYPE html>
|
NotFound = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -46,10 +51,28 @@ Please try again later.</p>
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getNotFoundPageContent() []byte {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if NotFoundPagePath != "" {
|
||||||
|
buf, err = ioutil.ReadFile(NotFoundPagePath)
|
||||||
|
if err != nil {
|
||||||
|
frpLog.Warn("read custom 404 page error: %v", err)
|
||||||
|
buf = []byte(NotFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf = []byte(NotFound)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
func notFoundResponse() *http.Response {
|
func notFoundResponse() *http.Response {
|
||||||
header := make(http.Header)
|
header := make(http.Header)
|
||||||
header.Set("server", "frp/"+version.Full())
|
header.Set("server", "frp/"+version.Full())
|
||||||
header.Set("Content-Type", "text/html")
|
header.Set("Content-Type", "text/html")
|
||||||
|
|
||||||
res := &http.Response{
|
res := &http.Response{
|
||||||
Status: "Not Found",
|
Status: "Not Found",
|
||||||
StatusCode: 404,
|
StatusCode: 404,
|
||||||
@@ -57,7 +80,7 @@ func notFoundResponse() *http.Response {
|
|||||||
ProtoMajor: 1,
|
ProtoMajor: 1,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Header: header,
|
Header: header,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(NotFound)),
|
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package vhost
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -17,13 +18,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// onExitFlushLoop is a callback set by tests to detect the state of the
|
|
||||||
// flushLoop() goroutine.
|
|
||||||
var onExitFlushLoop func()
|
|
||||||
|
|
||||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||||
// sends it to another server, proxying the response back to the
|
// sends it to another server, proxying the response back to the
|
||||||
// client.
|
// client.
|
||||||
@@ -44,12 +41,17 @@ type ReverseProxy struct {
|
|||||||
// to flush to the client while copying the
|
// to flush to the client while copying the
|
||||||
// response body.
|
// response body.
|
||||||
// If zero, no periodic flushing is done.
|
// If zero, no periodic flushing is done.
|
||||||
|
// A negative value means to flush immediately
|
||||||
|
// after each write to the client.
|
||||||
|
// The FlushInterval is ignored when ReverseProxy
|
||||||
|
// recognizes a response as a streaming response;
|
||||||
|
// for such responses, writes are flushed to the client
|
||||||
|
// immediately.
|
||||||
FlushInterval time.Duration
|
FlushInterval time.Duration
|
||||||
|
|
||||||
// ErrorLog specifies an optional logger for errors
|
// ErrorLog specifies an optional logger for errors
|
||||||
// that occur when attempting to proxy the request.
|
// that occur when attempting to proxy the request.
|
||||||
// If nil, logging goes to os.Stderr via the log package's
|
// If nil, logging is done via the log package's standard logger.
|
||||||
// standard logger.
|
|
||||||
ErrorLog *log.Logger
|
ErrorLog *log.Logger
|
||||||
|
|
||||||
// BufferPool optionally specifies a buffer pool to
|
// BufferPool optionally specifies a buffer pool to
|
||||||
@@ -57,12 +59,23 @@ type ReverseProxy struct {
|
|||||||
// copying HTTP response bodies.
|
// copying HTTP response bodies.
|
||||||
BufferPool BufferPool
|
BufferPool BufferPool
|
||||||
|
|
||||||
// ModifyResponse is an optional function that
|
// ModifyResponse is an optional function that modifies the
|
||||||
// modifies the Response from the backend.
|
// Response from the backend. It is called if the backend
|
||||||
// If it returns an error, the proxy returns a StatusBadGateway error.
|
// returns a response at all, with any HTTP status code.
|
||||||
|
// If the backend is unreachable, the optional ErrorHandler is
|
||||||
|
// called without any call to ModifyResponse.
|
||||||
|
//
|
||||||
|
// If ModifyResponse returns an error, ErrorHandler is called
|
||||||
|
// with its error value. If ErrorHandler is nil, its default
|
||||||
|
// implementation is used.
|
||||||
ModifyResponse func(*http.Response) error
|
ModifyResponse func(*http.Response) error
|
||||||
|
|
||||||
WebSocketDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
// ErrorHandler is an optional function that handles errors
|
||||||
|
// reaching the backend or errors from ModifyResponse.
|
||||||
|
//
|
||||||
|
// If nil, the default is to log the provided error and return
|
||||||
|
// a 502 Status Bad Gateway response.
|
||||||
|
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A BufferPool is an interface for getting and returning temporary
|
// A BufferPool is an interface for getting and returning temporary
|
||||||
@@ -118,18 +131,11 @@ func copyHeader(dst, src http.Header) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneHeader(h http.Header) http.Header {
|
|
||||||
h2 := make(http.Header, len(h))
|
|
||||||
for k, vv := range h {
|
|
||||||
vv2 := make([]string, len(vv))
|
|
||||||
copy(vv2, vv)
|
|
||||||
h2[k] = vv2
|
|
||||||
}
|
|
||||||
return h2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||||
|
// Connection header field. These are the headers defined by the
|
||||||
|
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
|
||||||
|
// compatibility.
|
||||||
var hopHeaders = []string{
|
var hopHeaders = []string{
|
||||||
"Connection",
|
"Connection",
|
||||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||||
@@ -137,54 +143,38 @@ var hopHeaders = []string{
|
|||||||
"Proxy-Authenticate",
|
"Proxy-Authenticate",
|
||||||
"Proxy-Authorization",
|
"Proxy-Authorization",
|
||||||
"Te", // canonicalized version of "TE"
|
"Te", // canonicalized version of "TE"
|
||||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
|
||||||
"Transfer-Encoding",
|
"Transfer-Encoding",
|
||||||
"Upgrade",
|
"Upgrade",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
p.logf("http: proxy error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
|
||||||
|
if p.ErrorHandler != nil {
|
||||||
|
return p.ErrorHandler
|
||||||
|
}
|
||||||
|
return p.defaultErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifyResponse conditionally runs the optional ModifyResponse hook
|
||||||
|
// and reports whether the request should proceed.
|
||||||
|
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
|
||||||
|
if p.ModifyResponse == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
|
res.Body.Close()
|
||||||
|
p.getErrorHandler()(rw, req, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if IsWebsocketRequest(req) {
|
|
||||||
p.serveWebSocket(rw, req)
|
|
||||||
} else {
|
|
||||||
p.serveHTTP(rw, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
if p.WebSocketDialContext == nil {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
|
||||||
|
|
||||||
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
|
||||||
if err != nil {
|
|
||||||
rw.WriteHeader(501)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer targetConn.Close()
|
|
||||||
|
|
||||||
p.Director(req)
|
|
||||||
|
|
||||||
hijacker, ok := rw.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn, _, errHijack := hijacker.Hijack()
|
|
||||||
if errHijack != nil {
|
|
||||||
rw.WriteHeader(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
req.Write(targetConn)
|
|
||||||
frpIo.Join(conn, targetConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
transport := p.Transport
|
transport := p.Transport
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
@@ -205,37 +195,49 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
|
outreq := req.WithContext(ctx)
|
||||||
if req.ContentLength == 0 {
|
if req.ContentLength == 0 {
|
||||||
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||||
}
|
}
|
||||||
|
|
||||||
outreq.Header = cloneHeader(req.Header)
|
// =============================
|
||||||
|
// Modified for frp
|
||||||
// Modify for frp
|
|
||||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
||||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
||||||
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "remote", req.RemoteAddr))
|
||||||
|
// =============================
|
||||||
|
|
||||||
p.Director(outreq)
|
p.Director(outreq)
|
||||||
outreq.Close = false
|
outreq.Close = false
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
reqUpType := upgradeType(outreq.Header)
|
||||||
// See RFC 2616, section 14.10.
|
removeConnectionHeaders(outreq.Header)
|
||||||
if c := outreq.Header.Get("Connection"); c != "" {
|
|
||||||
for _, f := range strings.Split(c, ",") {
|
|
||||||
if f = strings.TrimSpace(f); f != "" {
|
|
||||||
outreq.Header.Del(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove hop-by-hop headers to the backend. Especially
|
// Remove hop-by-hop headers to the backend. Especially
|
||||||
// important is "Connection" because we want a persistent
|
// important is "Connection" because we want a persistent
|
||||||
// connection, regardless of what the client sent to us.
|
// connection, regardless of what the client sent to us.
|
||||||
for _, h := range hopHeaders {
|
for _, h := range hopHeaders {
|
||||||
if outreq.Header.Get(h) != "" {
|
hv := outreq.Header.Get(h)
|
||||||
outreq.Header.Del(h)
|
if hv == "" {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if h == "Te" && hv == "trailers" {
|
||||||
|
// Issue 21096: tell backend applications that
|
||||||
|
// care about trailer support that we support
|
||||||
|
// trailers. (We do, but we don't go out of
|
||||||
|
// our way to advertise that unless the
|
||||||
|
// incoming client request thought it was
|
||||||
|
// worth mentioning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
outreq.Header.Del(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After stripping all the hop-by-hop connection headers above, add back any
|
||||||
|
// necessary for protocol upgrades, such as for websockets.
|
||||||
|
if reqUpType != "" {
|
||||||
|
outreq.Header.Set("Connection", "Upgrade")
|
||||||
|
outreq.Header.Set("Upgrade", reqUpType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
@@ -250,32 +252,27 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
res, err := transport.RoundTrip(outreq)
|
res, err := transport.RoundTrip(outreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logf("http: proxy error: %v", err)
|
p.getErrorHandler()(rw, outreq, err)
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
|
||||||
rw.Write([]byte(NotFound))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hop-by-hop headers listed in the
|
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||||
// "Connection" header of the response.
|
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||||
if c := res.Header.Get("Connection"); c != "" {
|
if !p.modifyResponse(rw, res, outreq) {
|
||||||
for _, f := range strings.Split(c, ",") {
|
return
|
||||||
if f = strings.TrimSpace(f); f != "" {
|
|
||||||
res.Header.Del(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
p.handleUpgradeResponse(rw, outreq, res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeConnectionHeaders(res.Header)
|
||||||
|
|
||||||
for _, h := range hopHeaders {
|
for _, h := range hopHeaders {
|
||||||
res.Header.Del(h)
|
res.Header.Del(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ModifyResponse != nil {
|
if !p.modifyResponse(rw, res, outreq) {
|
||||||
if err := p.ModifyResponse(res); err != nil {
|
return
|
||||||
p.logf("http: proxy error: %v", err)
|
|
||||||
rw.WriteHeader(http.StatusBadGateway)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copyHeader(rw.Header(), res.Header)
|
copyHeader(rw.Header(), res.Header)
|
||||||
@@ -292,6 +289,21 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rw.WriteHeader(res.StatusCode)
|
rw.WriteHeader(res.StatusCode)
|
||||||
|
|
||||||
|
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
|
||||||
|
if err != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
// Since we're streaming the response, if we run into an error all we can do
|
||||||
|
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
|
||||||
|
// on read error while copying body.
|
||||||
|
if !shouldPanicOnCopyError(req) {
|
||||||
|
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(http.ErrAbortHandler)
|
||||||
|
}
|
||||||
|
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||||
|
|
||||||
if len(res.Trailer) > 0 {
|
if len(res.Trailer) > 0 {
|
||||||
// Force chunking if we saw a response trailer.
|
// Force chunking if we saw a response trailer.
|
||||||
// This prevents net/http from calculating the length for short
|
// This prevents net/http from calculating the length for short
|
||||||
@@ -300,8 +312,6 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
fl.Flush()
|
fl.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.copyResponse(rw, res.Body)
|
|
||||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
|
||||||
|
|
||||||
if len(res.Trailer) == announcedTrailers {
|
if len(res.Trailer) == announcedTrailers {
|
||||||
copyHeader(rw.Header(), res.Trailer)
|
copyHeader(rw.Header(), res.Trailer)
|
||||||
@@ -316,16 +326,68 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
var inOurTests bool // whether we're in our own tests
|
||||||
if p.FlushInterval != 0 {
|
|
||||||
|
// shouldPanicOnCopyError reports whether the reverse proxy should
|
||||||
|
// panic with http.ErrAbortHandler. This is the right thing to do by
|
||||||
|
// default, but Go 1.10 and earlier did not, so existing unit tests
|
||||||
|
// weren't expecting panics. Only panic in our own tests, or when
|
||||||
|
// running under the HTTP server.
|
||||||
|
func shouldPanicOnCopyError(req *http.Request) bool {
|
||||||
|
if inOurTests {
|
||||||
|
// Our tests know to handle this panic.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if req.Context().Value(http.ServerContextKey) != nil {
|
||||||
|
// We seem to be running under an HTTP server, so
|
||||||
|
// it'll recover the panic.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Otherwise act like Go 1.10 and earlier to not break
|
||||||
|
// existing tests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h.
|
||||||
|
// See RFC 7230, section 6.1
|
||||||
|
func removeConnectionHeaders(h http.Header) {
|
||||||
|
for _, f := range h["Connection"] {
|
||||||
|
for _, sf := range strings.Split(f, ",") {
|
||||||
|
if sf = strings.TrimSpace(sf); sf != "" {
|
||||||
|
h.Del(sf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushInterval returns the p.FlushInterval value, conditionally
|
||||||
|
// overriding its value for a specific request/response.
|
||||||
|
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||||
|
resCT := res.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// For Server-Sent Events responses, flush immediately.
|
||||||
|
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
|
||||||
|
if resCT == "text/event-stream" {
|
||||||
|
return -1 // negative means immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: more specific cases? e.g. res.ContentLength == -1?
|
||||||
|
return p.FlushInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
|
||||||
|
if flushInterval != 0 {
|
||||||
if wf, ok := dst.(writeFlusher); ok {
|
if wf, ok := dst.(writeFlusher); ok {
|
||||||
mlw := &maxLatencyWriter{
|
mlw := &maxLatencyWriter{
|
||||||
dst: wf,
|
dst: wf,
|
||||||
latency: p.FlushInterval,
|
latency: flushInterval,
|
||||||
done: make(chan bool),
|
|
||||||
}
|
}
|
||||||
go mlw.flushLoop()
|
|
||||||
defer mlw.stop()
|
defer mlw.stop()
|
||||||
|
|
||||||
|
// set up initial timer so headers get flushed even if body writes are delayed
|
||||||
|
mlw.flushPending = true
|
||||||
|
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||||
|
|
||||||
dst = mlw
|
dst = mlw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,13 +395,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
|||||||
var buf []byte
|
var buf []byte
|
||||||
if p.BufferPool != nil {
|
if p.BufferPool != nil {
|
||||||
buf = p.BufferPool.Get()
|
buf = p.BufferPool.Get()
|
||||||
|
defer p.BufferPool.Put(buf)
|
||||||
}
|
}
|
||||||
p.copyBuffer(dst, src, buf)
|
_, err := p.copyBuffer(dst, src, buf)
|
||||||
if p.BufferPool != nil {
|
return err
|
||||||
p.BufferPool.Put(buf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyBuffer returns any write errors or non-EOF read errors, and the amount
|
||||||
|
// of bytes written.
|
||||||
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
buf = make([]byte, 32*1024)
|
buf = make([]byte, 32*1024)
|
||||||
@@ -363,6 +426,9 @@ func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
|
if rerr == io.EOF {
|
||||||
|
rerr = nil
|
||||||
|
}
|
||||||
return written, rerr
|
return written, rerr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,47 +449,115 @@ type writeFlusher interface {
|
|||||||
|
|
||||||
type maxLatencyWriter struct {
|
type maxLatencyWriter struct {
|
||||||
dst writeFlusher
|
dst writeFlusher
|
||||||
latency time.Duration
|
latency time.Duration // non-zero; negative means to flush immediately
|
||||||
|
|
||||||
mu sync.Mutex // protects Write + Flush
|
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||||
done chan bool
|
t *time.Timer
|
||||||
|
flushPending bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) Write(p []byte) (int, error) {
|
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
return m.dst.Write(p)
|
n, err = m.dst.Write(p)
|
||||||
|
if m.latency < 0 {
|
||||||
|
m.dst.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.flushPending {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.t == nil {
|
||||||
|
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||||
|
} else {
|
||||||
|
m.t.Reset(m.latency)
|
||||||
|
}
|
||||||
|
m.flushPending = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) flushLoop() {
|
func (m *maxLatencyWriter) delayedFlush() {
|
||||||
t := time.NewTicker(m.latency)
|
m.mu.Lock()
|
||||||
defer t.Stop()
|
defer m.mu.Unlock()
|
||||||
for {
|
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
|
||||||
select {
|
return
|
||||||
case <-m.done:
|
}
|
||||||
if onExitFlushLoop != nil {
|
m.dst.Flush()
|
||||||
onExitFlushLoop()
|
m.flushPending = false
|
||||||
}
|
}
|
||||||
return
|
|
||||||
case <-t.C:
|
func (m *maxLatencyWriter) stop() {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.dst.Flush()
|
defer m.mu.Unlock()
|
||||||
m.mu.Unlock()
|
m.flushPending = false
|
||||||
}
|
if m.t != nil {
|
||||||
|
m.t.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *maxLatencyWriter) stop() { m.done <- true }
|
func upgradeType(h http.Header) string {
|
||||||
|
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||||
func IsWebsocketRequest(req *http.Request) bool {
|
return ""
|
||||||
containsHeader := func(name, value string) bool {
|
|
||||||
items := strings.Split(req.Header.Get(name), ",")
|
|
||||||
for _, item := range items {
|
|
||||||
if value == strings.ToLower(strings.TrimSpace(item)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return containsHeader("Connection", "upgrade") && containsHeader("Upgrade", "websocket")
|
return strings.ToLower(h.Get("Upgrade"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||||
|
reqUpType := upgradeType(req.Header)
|
||||||
|
resUpType := upgradeType(res.Header)
|
||||||
|
if reqUpType != resUpType {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeader(res.Header, rw.Header())
|
||||||
|
|
||||||
|
hj, ok := rw.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||||
|
if !ok {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer backConn.Close()
|
||||||
|
conn, brw, err := hj.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
||||||
|
if err := res.Write(brw); err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := brw.Flush(); err != nil {
|
||||||
|
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
spc := switchProtocolCopier{user: conn, backend: backConn}
|
||||||
|
go spc.copyToBackend(errc)
|
||||||
|
go spc.copyFromBackend(errc)
|
||||||
|
<-errc
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// switchProtocolCopier exists so goroutines proxying data back and
|
||||||
|
// forth have nice names in stacks.
|
||||||
|
type switchProtocolCopier struct {
|
||||||
|
user, backend io.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||||
|
_, err := io.Copy(c.user, c.backend)
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||||
|
_, err := io.Copy(c.backend, c.user)
|
||||||
|
errc <- err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||||
|
)
|
||||||
|
|
||||||
type VhostRouters struct {
|
type VhostRouters struct {
|
||||||
RouterByDomain map[string][]*VhostRouter
|
RouterByDomain map[string][]*VhostRouter
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
@@ -24,10 +29,14 @@ func NewVhostRouters() *VhostRouters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
func (r *VhostRouters) Add(domain, location string, payload interface{}) error {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
|
if _, exist := r.exist(domain, location); exist {
|
||||||
|
return ErrRouterConfigConflict
|
||||||
|
}
|
||||||
|
|
||||||
vrs, found := r.RouterByDomain[domain]
|
vrs, found := r.RouterByDomain[domain]
|
||||||
if !found {
|
if !found {
|
||||||
vrs = make([]*VhostRouter, 0, 1)
|
vrs = make([]*VhostRouter, 0, 1)
|
||||||
@@ -42,6 +51,7 @@ func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
|||||||
|
|
||||||
sort.Sort(sort.Reverse(ByLocation(vrs)))
|
sort.Sort(sort.Reverse(ByLocation(vrs)))
|
||||||
r.RouterByDomain[domain] = vrs
|
r.RouterByDomain[domain] = vrs
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Del(domain, location string) {
|
func (r *VhostRouters) Del(domain, location string) {
|
||||||
@@ -80,10 +90,7 @@ func (r *VhostRouters) Get(host, path string) (vr *VhostRouter, exist bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Exist(host, path string) (vr *VhostRouter, exist bool) {
|
func (r *VhostRouters) exist(host, path string) (vr *VhostRouter, exist bool) {
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
vrs, found := r.RouterByDomain[host]
|
vrs, found := r.RouterByDomain[host]
|
||||||
if !found {
|
if !found {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ package vhost
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
@@ -35,7 +34,6 @@ type VhostMuxer struct {
|
|||||||
authFunc httpAuthFunc
|
authFunc httpAuthFunc
|
||||||
rewriteFunc hostRewriteFunc
|
rewriteFunc hostRewriteFunc
|
||||||
registryRouter *VhostRouters
|
registryRouter *VhostRouters
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
||||||
@@ -51,8 +49,9 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
|
|||||||
return mux, nil
|
return mux, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateConnFunc func() (frpNet.Conn, error)
|
type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
|
||||||
|
|
||||||
|
// VhostRouteConfig is the params used to match HTTP requests
|
||||||
type VhostRouteConfig struct {
|
type VhostRouteConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
Location string
|
Location string
|
||||||
@@ -67,14 +66,6 @@ type VhostRouteConfig struct {
|
|||||||
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
||||||
// then rewrite the host header to rewriteHost
|
// then rewrite the host header to rewriteHost
|
||||||
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
||||||
v.mutex.Lock()
|
|
||||||
defer v.mutex.Unlock()
|
|
||||||
|
|
||||||
_, ok := v.registryRouter.Exist(cfg.Domain, cfg.Location)
|
|
||||||
if ok {
|
|
||||||
return nil, fmt.Errorf("hostname [%s] location [%s] is already registered", cfg.Domain, cfg.Location)
|
|
||||||
}
|
|
||||||
|
|
||||||
l = &Listener{
|
l = &Listener{
|
||||||
name: cfg.Domain,
|
name: cfg.Domain,
|
||||||
location: cfg.Location,
|
location: cfg.Location,
|
||||||
@@ -85,14 +76,14 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
|
|||||||
accept: make(chan frpNet.Conn),
|
accept: make(chan frpNet.Conn),
|
||||||
Logger: log.NewPrefixLogger(""),
|
Logger: log.NewPrefixLogger(""),
|
||||||
}
|
}
|
||||||
v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
||||||
v.mutex.RLock()
|
|
||||||
defer v.mutex.RUnlock()
|
|
||||||
|
|
||||||
// first we check the full hostname
|
// first we check the full hostname
|
||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
vr, found := v.registryRouter.Get(name, path)
|
vr, found := v.registryRouter.Get(name, path)
|
||||||
|
|||||||
56
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
56
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
@@ -34,6 +34,7 @@ This library intents to provide a **smooth, resilient, ordered, error-checked an
|
|||||||
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode, which generates completely anonymous packet.
|
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode, which generates completely anonymous packet.
|
||||||
1. Only **A fixed number of goroutines** will be created for the entire server application, costs in **context switch** between goroutines have been taken into consideration.
|
1. Only **A fixed number of goroutines** will be created for the entire server application, costs in **context switch** between goroutines have been taken into consideration.
|
||||||
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with various improvements.
|
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with various improvements.
|
||||||
|
1. Platform-dependent optimizations: [sendmmsg](http://man7.org/linux/man-pages/man2/sendmmsg.2.html) and [recvmmsg](http://man7.org/linux/man-pages/man2/recvmmsg.2.html) were expoloited for linux.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -43,6 +44,24 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
|
|||||||
|
|
||||||
<img src="frame.png" alt="Frame Format" height="109px" />
|
<img src="frame.png" alt="Frame Format" height="109px" />
|
||||||
|
|
||||||
|
```
|
||||||
|
NONCE:
|
||||||
|
16bytes cryptographically secure random number, nonce changes for every packet.
|
||||||
|
|
||||||
|
CRC32:
|
||||||
|
CRC-32 checksum of data using the IEEE polynomial
|
||||||
|
|
||||||
|
FEC TYPE:
|
||||||
|
typeData = 0xF1
|
||||||
|
typeParity = 0xF2
|
||||||
|
|
||||||
|
FEC SEQID:
|
||||||
|
monotonically increasing in range: [0, (0xffffffff/shardSize) * shardSize - 1]
|
||||||
|
|
||||||
|
SIZE:
|
||||||
|
The size of KCP frame plus 2
|
||||||
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
+-----------------+
|
+-----------------+
|
||||||
| SESSION |
|
| SESSION |
|
||||||
@@ -65,16 +84,11 @@ For complete documentation, see the associated [Godoc](https://godoc.org/github.
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Examples
|
||||||
|
|
||||||
Client: [full demo](https://github.com/xtaci/kcptun/blob/master/client/main.go)
|
1. [simple examples](https://github.com/xtaci/kcp-go/tree/master/examples)
|
||||||
```go
|
2. [kcptun client](https://github.com/xtaci/kcptun/blob/master/client/main.go)
|
||||||
kcpconn, err := kcp.DialWithOptions("192.168.0.1:10000", nil, 10, 3)
|
3. [kcptun server](https://github.com/xtaci/kcptun/blob/master/server/main.go)
|
||||||
```
|
|
||||||
Server: [full demo](https://github.com/xtaci/kcptun/blob/master/server/main.go)
|
|
||||||
```go
|
|
||||||
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benchmark
|
## Benchmark
|
||||||
```
|
```
|
||||||
@@ -128,6 +142,10 @@ PASS
|
|||||||
ok github.com/xtaci/kcp-go 50.349s
|
ok github.com/xtaci/kcp-go 50.349s
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Typical Flame Graph
|
||||||
|

|
||||||
|
|
||||||
## Key Design Considerations
|
## Key Design Considerations
|
||||||
|
|
||||||
1. slice vs. container/list
|
1. slice vs. container/list
|
||||||
@@ -159,6 +177,18 @@ BenchmarkNow-4 100000000 15.6 ns/op
|
|||||||
|
|
||||||
In kcp-go, after each `kcp.output()` function call, current clock time will be updated upon return, and for a single `kcp.flush()` operation, current time will be queried from system once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(a fixed cost while no packet needs to be sent), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
|
In kcp-go, after each `kcp.output()` function call, current clock time will be updated upon return, and for a single `kcp.flush()` operation, current time will be queried from system once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(a fixed cost while no packet needs to be sent), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
|
||||||
|
|
||||||
|
3. Memory management
|
||||||
|
|
||||||
|
Primary memory allocation are done from a global buffer pool xmit.Buf, in kcp-go, when we need to allocate some bytes, we can get from that pool, and a fixed-capacity 1500 bytes(mtuLimit) will be returned, the rx queue, tx queue and fec queue all receive bytes from there, and they will return the bytes to the pool after using to prevent unnecessary zer0ing of bytes. The pool mechanism maintained a high watermark for slice objects, these in-flight objects from the pool will survive from the perodical garbage collection, meanwhile the pool kept the ability to return the memory to runtime if in idle.
|
||||||
|
|
||||||
|
4. Information security
|
||||||
|
|
||||||
|
kcp-go is shipped with builtin packet encryption powered by various block encryption algorithms and works in [Cipher Feedback Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_(CFB)), for each packet to be sent, the encryption process will start from encrypting a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) from the [system entropy](https://en.wikipedia.org/wiki//dev/random), so encryption to same plaintexts never leads to a same ciphertexts thereafter.
|
||||||
|
|
||||||
|
The contents of the packets are completely anonymous with encryption, including the headers(FEC,KCP), checksums and contents. Note that, no matter which encryption method you choose on you upper layer, if you disable encryption, the transmit will be insecure somehow, since the header is ***PLAINTEXT*** to everyone it would be susceptible to header tampering, such as jamming the *sliding window size*, *round-trip time*, *FEC property* and *checksums*. ```AES-128``` is suggested for minimal encryption since modern CPUs are shipped with [AES-NI](https://en.wikipedia.org/wiki/AES_instruction_set) instructions and performs even better than `salsa20`(check the table above).
|
||||||
|
|
||||||
|
Other possible attacks to kcp-go includes: a) [traffic analysis](https://en.wikipedia.org/wiki/Traffic_analysis), dataflow on specific websites may have pattern while interchanging data, but this type of eavesdropping has been mitigated by adapting [smux](https://github.com/xtaci/smux) to mix data streams so as to introduce noises, perfect solution to this has not appeared yet, theroretically by shuffling/mixing messages on larger scale network may mitigate this problem. b) [replay attack](https://en.wikipedia.org/wiki/Replay_attack), since the asymmetrical encryption has not been introduced into kcp-go for some reason, capturing the packets and replay them on a different machine is possible, (notice: hijacking the session and decrypting the contents is still *impossible*), so upper layers should contain a asymmetrical encryption system to guarantee the authenticity of each message(to process message exactly once), such as HTTPS/OpenSSL/LibreSSL, only by signing the requests with private keys can eliminate this type of attack.
|
||||||
|
|
||||||
## Connection Termination
|
## Connection Termination
|
||||||
|
|
||||||
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
|
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
|
||||||
@@ -169,6 +199,14 @@ Q: I'm handling >5K connections on my server, the CPU utilization is so high.
|
|||||||
|
|
||||||
A: A standalone `agent` or `gate` server for running kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements(timing) which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load, but lower the performance.
|
A: A standalone `agent` or `gate` server for running kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements(timing) which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load, but lower the performance.
|
||||||
|
|
||||||
|
Q: When should I enable FEC?
|
||||||
|
|
||||||
|
A: Forward error correction is critical to long-distance transmission, because a packet loss will lead to a huge penalty in time. And for the complicated packet routing network in modern world, round-trip time based loss check will not always be efficient, the big deviation of RTT samples in the long way usually leads to a larger RTO value in typical rtt estimator, which in other words, slows down the transmission.
|
||||||
|
|
||||||
|
Q: Should I enable encryption?
|
||||||
|
|
||||||
|
A: Yes, for the safety of protocol, even if the upper layer has encrypted.
|
||||||
|
|
||||||
## Who is using this?
|
## Who is using this?
|
||||||
|
|
||||||
1. https://github.com/xtaci/kcptun -- A Secure Tunnel Based On KCP over UDP.
|
1. https://github.com/xtaci/kcptun -- A Secure Tunnel Based On KCP over UDP.
|
||||||
|
|||||||
12
vendor/github.com/fatedier/kcp-go/batchconn.go
generated
vendored
Normal file
12
vendor/github.com/fatedier/kcp-go/batchconn.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package kcp
|
||||||
|
|
||||||
|
import "golang.org/x/net/ipv4"
|
||||||
|
|
||||||
|
const (
|
||||||
|
batchSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type batchConn interface {
|
||||||
|
WriteBatch(ms []ipv4.Message, flags int) (int, error)
|
||||||
|
ReadBatch(ms []ipv4.Message, flags int) (int, error)
|
||||||
|
}
|
||||||
115
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
115
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
@@ -11,36 +11,34 @@ const (
|
|||||||
fecHeaderSize = 6
|
fecHeaderSize = 6
|
||||||
fecHeaderSizePlus2 = fecHeaderSize + 2 // plus 2B data size
|
fecHeaderSizePlus2 = fecHeaderSize + 2 // plus 2B data size
|
||||||
typeData = 0xf1
|
typeData = 0xf1
|
||||||
typeFEC = 0xf2
|
typeParity = 0xf2
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// fecPacket is a decoded FEC packet
|
||||||
// fecPacket is a decoded FEC packet
|
type fecPacket []byte
|
||||||
fecPacket struct {
|
|
||||||
seqid uint32
|
|
||||||
flag uint16
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// fecDecoder for decoding incoming packets
|
func (bts fecPacket) seqid() uint32 { return binary.LittleEndian.Uint32(bts) }
|
||||||
fecDecoder struct {
|
func (bts fecPacket) flag() uint16 { return binary.LittleEndian.Uint16(bts[4:]) }
|
||||||
rxlimit int // queue size limit
|
func (bts fecPacket) data() []byte { return bts[6:] }
|
||||||
dataShards int
|
|
||||||
parityShards int
|
|
||||||
shardSize int
|
|
||||||
rx []fecPacket // ordered receive queue
|
|
||||||
|
|
||||||
// caches
|
// fecDecoder for decoding incoming packets
|
||||||
decodeCache [][]byte
|
type fecDecoder struct {
|
||||||
flagCache []bool
|
rxlimit int // queue size limit
|
||||||
|
dataShards int
|
||||||
|
parityShards int
|
||||||
|
shardSize int
|
||||||
|
rx []fecPacket // ordered receive queue
|
||||||
|
|
||||||
// zeros
|
// caches
|
||||||
zeros []byte
|
decodeCache [][]byte
|
||||||
|
flagCache []bool
|
||||||
|
|
||||||
// RS decoder
|
// zeros
|
||||||
codec reedsolomon.Encoder
|
zeros []byte
|
||||||
}
|
|
||||||
)
|
// RS decoder
|
||||||
|
codec reedsolomon.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
||||||
if dataShards <= 0 || parityShards <= 0 {
|
if dataShards <= 0 || parityShards <= 0 {
|
||||||
@@ -66,33 +64,24 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
|||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeBytes a fec packet
|
|
||||||
func (dec *fecDecoder) decodeBytes(data []byte) fecPacket {
|
|
||||||
var pkt fecPacket
|
|
||||||
pkt.seqid = binary.LittleEndian.Uint32(data)
|
|
||||||
pkt.flag = binary.LittleEndian.Uint16(data[4:])
|
|
||||||
// allocate memory & copy
|
|
||||||
buf := xmitBuf.Get().([]byte)[:len(data)-6]
|
|
||||||
copy(buf, data[6:])
|
|
||||||
pkt.data = buf
|
|
||||||
return pkt
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode a fec packet
|
// decode a fec packet
|
||||||
func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
func (dec *fecDecoder) decode(in fecPacket) (recovered [][]byte) {
|
||||||
// insertion
|
// insertion
|
||||||
n := len(dec.rx) - 1
|
n := len(dec.rx) - 1
|
||||||
insertIdx := 0
|
insertIdx := 0
|
||||||
for i := n; i >= 0; i-- {
|
for i := n; i >= 0; i-- {
|
||||||
if pkt.seqid == dec.rx[i].seqid { // de-duplicate
|
if in.seqid() == dec.rx[i].seqid() { // de-duplicate
|
||||||
xmitBuf.Put(pkt.data)
|
|
||||||
return nil
|
return nil
|
||||||
} else if _itimediff(pkt.seqid, dec.rx[i].seqid) > 0 { // insertion
|
} else if _itimediff(in.seqid(), dec.rx[i].seqid()) > 0 { // insertion
|
||||||
insertIdx = i + 1
|
insertIdx = i + 1
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make a copy
|
||||||
|
pkt := fecPacket(xmitBuf.Get().([]byte)[:len(in)])
|
||||||
|
copy(pkt, in)
|
||||||
|
|
||||||
// insert into ordered rx queue
|
// insert into ordered rx queue
|
||||||
if insertIdx == n+1 {
|
if insertIdx == n+1 {
|
||||||
dec.rx = append(dec.rx, pkt)
|
dec.rx = append(dec.rx, pkt)
|
||||||
@@ -103,11 +92,11 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shard range for current packet
|
// shard range for current packet
|
||||||
shardBegin := pkt.seqid - pkt.seqid%uint32(dec.shardSize)
|
shardBegin := pkt.seqid() - pkt.seqid()%uint32(dec.shardSize)
|
||||||
shardEnd := shardBegin + uint32(dec.shardSize) - 1
|
shardEnd := shardBegin + uint32(dec.shardSize) - 1
|
||||||
|
|
||||||
// max search range in ordered queue for current shard
|
// max search range in ordered queue for current shard
|
||||||
searchBegin := insertIdx - int(pkt.seqid%uint32(dec.shardSize))
|
searchBegin := insertIdx - int(pkt.seqid()%uint32(dec.shardSize))
|
||||||
if searchBegin < 0 {
|
if searchBegin < 0 {
|
||||||
searchBegin = 0
|
searchBegin = 0
|
||||||
}
|
}
|
||||||
@@ -130,21 +119,21 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
|
|
||||||
// shard assembly
|
// shard assembly
|
||||||
for i := searchBegin; i <= searchEnd; i++ {
|
for i := searchBegin; i <= searchEnd; i++ {
|
||||||
seqid := dec.rx[i].seqid
|
seqid := dec.rx[i].seqid()
|
||||||
if _itimediff(seqid, shardEnd) > 0 {
|
if _itimediff(seqid, shardEnd) > 0 {
|
||||||
break
|
break
|
||||||
} else if _itimediff(seqid, shardBegin) >= 0 {
|
} else if _itimediff(seqid, shardBegin) >= 0 {
|
||||||
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data
|
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data()
|
||||||
shardsflag[seqid%uint32(dec.shardSize)] = true
|
shardsflag[seqid%uint32(dec.shardSize)] = true
|
||||||
numshard++
|
numshard++
|
||||||
if dec.rx[i].flag == typeData {
|
if dec.rx[i].flag() == typeData {
|
||||||
numDataShard++
|
numDataShard++
|
||||||
}
|
}
|
||||||
if numshard == 1 {
|
if numshard == 1 {
|
||||||
first = i
|
first = i
|
||||||
}
|
}
|
||||||
if len(dec.rx[i].data) > maxlen {
|
if len(dec.rx[i].data()) > maxlen {
|
||||||
maxlen = len(dec.rx[i].data)
|
maxlen = len(dec.rx[i].data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,11 +148,14 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
dlen := len(shards[k])
|
dlen := len(shards[k])
|
||||||
shards[k] = shards[k][:maxlen]
|
shards[k] = shards[k][:maxlen]
|
||||||
copy(shards[k][dlen:], dec.zeros)
|
copy(shards[k][dlen:], dec.zeros)
|
||||||
|
} else {
|
||||||
|
shards[k] = xmitBuf.Get().([]byte)[:0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := dec.codec.ReconstructData(shards); err == nil {
|
if err := dec.codec.ReconstructData(shards); err == nil {
|
||||||
for k := range shards[:dec.dataShards] {
|
for k := range shards[:dec.dataShards] {
|
||||||
if !shardsflag[k] {
|
if !shardsflag[k] {
|
||||||
|
// recovered data should be recycled
|
||||||
recovered = append(recovered, shards[k])
|
recovered = append(recovered, shards[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +166,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
|
|
||||||
// keep rxlimit
|
// keep rxlimit
|
||||||
if len(dec.rx) > dec.rxlimit {
|
if len(dec.rx) > dec.rxlimit {
|
||||||
if dec.rx[0].flag == typeData { // track the unrecoverable data
|
if dec.rx[0].flag() == typeData { // track the unrecoverable data
|
||||||
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
|
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
|
||||||
}
|
}
|
||||||
dec.rx = dec.freeRange(0, 1, dec.rx)
|
dec.rx = dec.freeRange(0, 1, dec.rx)
|
||||||
@@ -182,15 +174,16 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// free a range of fecPacket, and zero for GC recycling
|
// free a range of fecPacket
|
||||||
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||||
for i := first; i < first+n; i++ { // recycle buffer
|
for i := first; i < first+n; i++ { // recycle buffer
|
||||||
xmitBuf.Put(q[i].data)
|
xmitBuf.Put([]byte(q[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if first == 0 && n < cap(q)/2 {
|
||||||
|
return q[n:]
|
||||||
}
|
}
|
||||||
copy(q[first:], q[first+n:])
|
copy(q[first:], q[first+n:])
|
||||||
for i := 0; i < n; i++ { // dereference data
|
|
||||||
q[len(q)-1-i].data = nil
|
|
||||||
}
|
|
||||||
return q[:len(q)-n]
|
return q[:len(q)-n]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +222,7 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
|||||||
enc.dataShards = dataShards
|
enc.dataShards = dataShards
|
||||||
enc.parityShards = parityShards
|
enc.parityShards = parityShards
|
||||||
enc.shardSize = dataShards + parityShards
|
enc.shardSize = dataShards + parityShards
|
||||||
enc.paws = (0xffffffff/uint32(enc.shardSize) - 1) * uint32(enc.shardSize)
|
enc.paws = 0xffffffff / uint32(enc.shardSize) * uint32(enc.shardSize)
|
||||||
enc.headerOffset = offset
|
enc.headerOffset = offset
|
||||||
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
||||||
|
|
||||||
@@ -252,13 +245,16 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
|||||||
// encodes the packet, outputs parity shards if we have collected quorum datashards
|
// encodes the packet, outputs parity shards if we have collected quorum datashards
|
||||||
// notice: the contents of 'ps' will be re-written in successive calling
|
// notice: the contents of 'ps' will be re-written in successive calling
|
||||||
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||||
|
// The header format:
|
||||||
|
// | FEC SEQID(4B) | FEC TYPE(2B) | SIZE (2B) | PAYLOAD(SIZE-2) |
|
||||||
|
// |<-headerOffset |<-payloadOffset
|
||||||
enc.markData(b[enc.headerOffset:])
|
enc.markData(b[enc.headerOffset:])
|
||||||
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
||||||
|
|
||||||
// copy data to fec datashards
|
// copy data from payloadOffset to fec shard cache
|
||||||
sz := len(b)
|
sz := len(b)
|
||||||
enc.shardCache[enc.shardCount] = enc.shardCache[enc.shardCount][:sz]
|
enc.shardCache[enc.shardCount] = enc.shardCache[enc.shardCount][:sz]
|
||||||
copy(enc.shardCache[enc.shardCount], b)
|
copy(enc.shardCache[enc.shardCount][enc.payloadOffset:], b[enc.payloadOffset:])
|
||||||
enc.shardCount++
|
enc.shardCount++
|
||||||
|
|
||||||
// track max datashard length
|
// track max datashard length
|
||||||
@@ -285,7 +281,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
|||||||
if err := enc.codec.Encode(cache); err == nil {
|
if err := enc.codec.Encode(cache); err == nil {
|
||||||
ps = enc.shardCache[enc.dataShards:]
|
ps = enc.shardCache[enc.dataShards:]
|
||||||
for k := range ps {
|
for k := range ps {
|
||||||
enc.markFEC(ps[k][enc.headerOffset:])
|
enc.markParity(ps[k][enc.headerOffset:])
|
||||||
ps[k] = ps[k][:enc.maxSize]
|
ps[k] = ps[k][:enc.maxSize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,8 +300,9 @@ func (enc *fecEncoder) markData(data []byte) {
|
|||||||
enc.next++
|
enc.next++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *fecEncoder) markFEC(data []byte) {
|
func (enc *fecEncoder) markParity(data []byte) {
|
||||||
binary.LittleEndian.PutUint32(data, enc.next)
|
binary.LittleEndian.PutUint32(data, enc.next)
|
||||||
binary.LittleEndian.PutUint16(data[4:], typeFEC)
|
binary.LittleEndian.PutUint16(data[4:], typeParity)
|
||||||
|
// sequence wrap will only happen at parity shard
|
||||||
enc.next = (enc.next + 1) % enc.paws
|
enc.next = (enc.next + 1) % enc.paws
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
vendor/github.com/fatedier/kcp-go/flame.png
generated
vendored
Normal file
BIN
vendor/github.com/fatedier/kcp-go/flame.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
147
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
147
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
@@ -1,9 +1,9 @@
|
|||||||
// Package kcp - A Fast and Reliable ARQ Protocol
|
|
||||||
package kcp
|
package kcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -30,6 +30,12 @@ const (
|
|||||||
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
|
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// monotonic reference time point
|
||||||
|
var refTime time.Time = time.Now()
|
||||||
|
|
||||||
|
// currentMs returns current elasped monotonic milliseconds since program startup
|
||||||
|
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
|
||||||
|
|
||||||
// output_callback is a prototype which ought capture conn and call conn.Write
|
// output_callback is a prototype which ought capture conn and call conn.Write
|
||||||
type output_callback func(buf []byte, size int)
|
type output_callback func(buf []byte, size int)
|
||||||
|
|
||||||
@@ -145,8 +151,9 @@ type KCP struct {
|
|||||||
|
|
||||||
acklist []ackItem
|
acklist []ackItem
|
||||||
|
|
||||||
buffer []byte
|
buffer []byte
|
||||||
output output_callback
|
reserved int
|
||||||
|
output output_callback
|
||||||
}
|
}
|
||||||
|
|
||||||
type ackItem struct {
|
type ackItem struct {
|
||||||
@@ -154,8 +161,11 @@ type ackItem struct {
|
|||||||
ts uint32
|
ts uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKCP create a new kcp control object, 'conv' must equal in two endpoint
|
// NewKCP create a new kcp state machine
|
||||||
// from the same connection.
|
//
|
||||||
|
// 'conv' must be equal in the connection peers, or else data will be silently rejected.
|
||||||
|
//
|
||||||
|
// 'output' function will be called whenever these is data to be sent on wire.
|
||||||
func NewKCP(conv uint32, output output_callback) *KCP {
|
func NewKCP(conv uint32, output output_callback) *KCP {
|
||||||
kcp := new(KCP)
|
kcp := new(KCP)
|
||||||
kcp.conv = conv
|
kcp.conv = conv
|
||||||
@@ -164,7 +174,7 @@ func NewKCP(conv uint32, output output_callback) *KCP {
|
|||||||
kcp.rmt_wnd = IKCP_WND_RCV
|
kcp.rmt_wnd = IKCP_WND_RCV
|
||||||
kcp.mtu = IKCP_MTU_DEF
|
kcp.mtu = IKCP_MTU_DEF
|
||||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
||||||
kcp.buffer = make([]byte, (kcp.mtu+IKCP_OVERHEAD)*3)
|
kcp.buffer = make([]byte, kcp.mtu)
|
||||||
kcp.rx_rto = IKCP_RTO_DEF
|
kcp.rx_rto = IKCP_RTO_DEF
|
||||||
kcp.rx_minrto = IKCP_RTO_MIN
|
kcp.rx_minrto = IKCP_RTO_MIN
|
||||||
kcp.interval = IKCP_INTERVAL
|
kcp.interval = IKCP_INTERVAL
|
||||||
@@ -189,6 +199,19 @@ func (kcp *KCP) delSegment(seg *segment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReserveBytes keeps n bytes untouched from the beginning of the buffer,
|
||||||
|
// the output_callback function should be aware of this.
|
||||||
|
//
|
||||||
|
// Return false if n >= mss
|
||||||
|
func (kcp *KCP) ReserveBytes(n int) bool {
|
||||||
|
if n >= int(kcp.mtu-IKCP_OVERHEAD) || n < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
kcp.reserved = n
|
||||||
|
kcp.mss = kcp.mtu - IKCP_OVERHEAD - uint32(n)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// PeekSize checks the size of next message in the recv queue
|
// PeekSize checks the size of next message in the recv queue
|
||||||
func (kcp *KCP) PeekSize() (length int) {
|
func (kcp *KCP) PeekSize() (length int) {
|
||||||
if len(kcp.rcv_queue) == 0 {
|
if len(kcp.rcv_queue) == 0 {
|
||||||
@@ -214,19 +237,21 @@ func (kcp *KCP) PeekSize() (length int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recv is user/upper level recv: returns size, returns below zero for EAGAIN
|
// Receive data from kcp state machine
|
||||||
|
//
|
||||||
|
// Return number of bytes read.
|
||||||
|
//
|
||||||
|
// Return -1 when there is no readable data.
|
||||||
|
//
|
||||||
|
// Return -2 if len(buffer) is smaller than kcp.PeekSize().
|
||||||
func (kcp *KCP) Recv(buffer []byte) (n int) {
|
func (kcp *KCP) Recv(buffer []byte) (n int) {
|
||||||
if len(kcp.rcv_queue) == 0 {
|
peeksize := kcp.PeekSize()
|
||||||
|
if peeksize < 0 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
peeksize := kcp.PeekSize()
|
|
||||||
if peeksize < 0 {
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
|
|
||||||
if peeksize > len(buffer) {
|
if peeksize > len(buffer) {
|
||||||
return -3
|
return -2
|
||||||
}
|
}
|
||||||
|
|
||||||
var fast_recover bool
|
var fast_recover bool
|
||||||
@@ -255,7 +280,7 @@ func (kcp *KCP) Recv(buffer []byte) (n int) {
|
|||||||
count = 0
|
count = 0
|
||||||
for k := range kcp.rcv_buf {
|
for k := range kcp.rcv_buf {
|
||||||
seg := &kcp.rcv_buf[k]
|
seg := &kcp.rcv_buf[k]
|
||||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue)+count < int(kcp.rcv_wnd) {
|
||||||
kcp.rcv_nxt++
|
kcp.rcv_nxt++
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
@@ -386,6 +411,10 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
|||||||
for k := range kcp.snd_buf {
|
for k := range kcp.snd_buf {
|
||||||
seg := &kcp.snd_buf[k]
|
seg := &kcp.snd_buf[k]
|
||||||
if sn == seg.sn {
|
if sn == seg.sn {
|
||||||
|
// mark and free space, but leave the segment here,
|
||||||
|
// and wait until `una` to delete this, then we don't
|
||||||
|
// have to shift the segments behind forward,
|
||||||
|
// which is an expensive operation for large window
|
||||||
seg.acked = 1
|
seg.acked = 1
|
||||||
kcp.delSegment(seg)
|
kcp.delSegment(seg)
|
||||||
break
|
break
|
||||||
@@ -474,7 +503,7 @@ func (kcp *KCP) parse_data(newseg segment) bool {
|
|||||||
count := 0
|
count := 0
|
||||||
for k := range kcp.rcv_buf {
|
for k := range kcp.rcv_buf {
|
||||||
seg := &kcp.rcv_buf[k]
|
seg := &kcp.rcv_buf[k]
|
||||||
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue) < int(kcp.rcv_wnd) {
|
if seg.sn == kcp.rcv_nxt && len(kcp.rcv_queue)+count < int(kcp.rcv_wnd) {
|
||||||
kcp.rcv_nxt++
|
kcp.rcv_nxt++
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
@@ -489,8 +518,12 @@ func (kcp *KCP) parse_data(newseg segment) bool {
|
|||||||
return repeat
|
return repeat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input when you received a low level packet (eg. UDP packet), call it
|
// Input a packet into kcp state machine.
|
||||||
// regular indicates a regular packet has received(not from FEC)
|
//
|
||||||
|
// 'regular' indicates it's a real data packet from remote, and it means it's not generated from ReedSolomon
|
||||||
|
// codecs.
|
||||||
|
//
|
||||||
|
// 'ackNoDelay' will trigger immediate ACK, but surely it will not be efficient in bandwidth
|
||||||
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||||
snd_una := kcp.snd_una
|
snd_una := kcp.snd_una
|
||||||
if len(data) < IKCP_OVERHEAD {
|
if len(data) < IKCP_OVERHEAD {
|
||||||
@@ -634,14 +667,28 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
seg.una = kcp.rcv_nxt
|
seg.una = kcp.rcv_nxt
|
||||||
|
|
||||||
buffer := kcp.buffer
|
buffer := kcp.buffer
|
||||||
// flush acknowledges
|
ptr := buffer[kcp.reserved:] // keep n bytes untouched
|
||||||
ptr := buffer
|
|
||||||
for i, ack := range kcp.acklist {
|
// makeSpace makes room for writing
|
||||||
|
makeSpace := func(space int) {
|
||||||
size := len(buffer) - len(ptr)
|
size := len(buffer) - len(ptr)
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
if size+space > int(kcp.mtu) {
|
||||||
kcp.output(buffer, size)
|
kcp.output(buffer, size)
|
||||||
ptr = buffer
|
ptr = buffer[kcp.reserved:]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush bytes in buffer if there is any
|
||||||
|
flushBuffer := func() {
|
||||||
|
size := len(buffer) - len(ptr)
|
||||||
|
if size > kcp.reserved {
|
||||||
|
kcp.output(buffer, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush acknowledges
|
||||||
|
for i, ack := range kcp.acklist {
|
||||||
|
makeSpace(IKCP_OVERHEAD)
|
||||||
// filter jitters caused by bufferbloat
|
// filter jitters caused by bufferbloat
|
||||||
if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
|
if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
|
||||||
seg.sn, seg.ts = ack.sn, ack.ts
|
seg.sn, seg.ts = ack.sn, ack.ts
|
||||||
@@ -651,10 +698,7 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
kcp.acklist = kcp.acklist[0:0]
|
kcp.acklist = kcp.acklist[0:0]
|
||||||
|
|
||||||
if ackOnly { // flash remain ack segments
|
if ackOnly { // flash remain ack segments
|
||||||
size := len(buffer) - len(ptr)
|
flushBuffer()
|
||||||
if size > 0 {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
}
|
|
||||||
return kcp.interval
|
return kcp.interval
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,22 +729,14 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
// flush window probing commands
|
// flush window probing commands
|
||||||
if (kcp.probe & IKCP_ASK_SEND) != 0 {
|
if (kcp.probe & IKCP_ASK_SEND) != 0 {
|
||||||
seg.cmd = IKCP_CMD_WASK
|
seg.cmd = IKCP_CMD_WASK
|
||||||
size := len(buffer) - len(ptr)
|
makeSpace(IKCP_OVERHEAD)
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
ptr = seg.encode(ptr)
|
ptr = seg.encode(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush window probing commands
|
// flush window probing commands
|
||||||
if (kcp.probe & IKCP_ASK_TELL) != 0 {
|
if (kcp.probe & IKCP_ASK_TELL) != 0 {
|
||||||
seg.cmd = IKCP_CMD_WINS
|
seg.cmd = IKCP_CMD_WINS
|
||||||
size := len(buffer) - len(ptr)
|
makeSpace(IKCP_OVERHEAD)
|
||||||
if size+IKCP_OVERHEAD > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
ptr = seg.encode(ptr)
|
ptr = seg.encode(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,20 +815,14 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if needsend {
|
if needsend {
|
||||||
current = currentMs() // time update for a blocking call
|
current = currentMs()
|
||||||
segment.xmit++
|
segment.xmit++
|
||||||
segment.ts = current
|
segment.ts = current
|
||||||
segment.wnd = seg.wnd
|
segment.wnd = seg.wnd
|
||||||
segment.una = seg.una
|
segment.una = seg.una
|
||||||
|
|
||||||
size := len(buffer) - len(ptr)
|
|
||||||
need := IKCP_OVERHEAD + len(segment.data)
|
need := IKCP_OVERHEAD + len(segment.data)
|
||||||
|
makeSpace(need)
|
||||||
if size+need > int(kcp.mtu) {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
ptr = buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr = segment.encode(ptr)
|
ptr = segment.encode(ptr)
|
||||||
copy(ptr, segment.data)
|
copy(ptr, segment.data)
|
||||||
ptr = ptr[len(segment.data):]
|
ptr = ptr[len(segment.data):]
|
||||||
@@ -809,10 +839,7 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flash remain segments
|
// flash remain segments
|
||||||
size := len(buffer) - len(ptr)
|
flushBuffer()
|
||||||
if size > 0 {
|
|
||||||
kcp.output(buffer, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// counter updates
|
// counter updates
|
||||||
sum := lostSegs
|
sum := lostSegs
|
||||||
@@ -864,6 +891,8 @@ func (kcp *KCP) flush(ackOnly bool) uint32 {
|
|||||||
return uint32(minrto)
|
return uint32(minrto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (deprecated)
|
||||||
|
//
|
||||||
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
|
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
|
||||||
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
||||||
// 'current' - current timestamp in millisec.
|
// 'current' - current timestamp in millisec.
|
||||||
@@ -892,6 +921,8 @@ func (kcp *KCP) Update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (deprecated)
|
||||||
|
//
|
||||||
// Check determines when should you invoke ikcp_update:
|
// Check determines when should you invoke ikcp_update:
|
||||||
// returns when you should invoke ikcp_update in millisec, if there
|
// returns when you should invoke ikcp_update in millisec, if there
|
||||||
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
||||||
@@ -947,12 +978,16 @@ func (kcp *KCP) SetMtu(mtu int) int {
|
|||||||
if mtu < 50 || mtu < IKCP_OVERHEAD {
|
if mtu < 50 || mtu < IKCP_OVERHEAD {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
buffer := make([]byte, (mtu+IKCP_OVERHEAD)*3)
|
if kcp.reserved >= int(kcp.mtu-IKCP_OVERHEAD) || kcp.reserved < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, mtu)
|
||||||
if buffer == nil {
|
if buffer == nil {
|
||||||
return -2
|
return -2
|
||||||
}
|
}
|
||||||
kcp.mtu = uint32(mtu)
|
kcp.mtu = uint32(mtu)
|
||||||
kcp.mss = kcp.mtu - IKCP_OVERHEAD
|
kcp.mss = kcp.mtu - IKCP_OVERHEAD - uint32(kcp.reserved)
|
||||||
kcp.buffer = buffer
|
kcp.buffer = buffer
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1006,7 +1041,13 @@ func (kcp *KCP) WaitSnd() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove front n elements from queue
|
// remove front n elements from queue
|
||||||
|
// if the number of elements to remove is more than half of the size.
|
||||||
|
// just shift the rear elements to front, otherwise just reslice q to q[n:]
|
||||||
|
// then the cost of runtime.growslice can always be less than n/2
|
||||||
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
||||||
newn := copy(q, q[n:])
|
if n > cap(q)/2 {
|
||||||
return q[:newn]
|
newn := copy(q, q[n:])
|
||||||
|
return q[:newn]
|
||||||
|
}
|
||||||
|
return q[n:]
|
||||||
}
|
}
|
||||||
|
|||||||
48
vendor/github.com/fatedier/kcp-go/readloop.go
generated
vendored
Normal file
48
vendor/github.com/fatedier/kcp-go/readloop.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *UDPSession) defaultReadLoop() {
|
||||||
|
buf := make([]byte, mtuLimit)
|
||||||
|
var src string
|
||||||
|
for {
|
||||||
|
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
||||||
|
// make sure the packet is from the same source
|
||||||
|
if src == "" { // set source address
|
||||||
|
src = addr.String()
|
||||||
|
} else if addr.String() != src {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n >= s.headerSize+IKCP_OVERHEAD {
|
||||||
|
s.packetInput(buf[:n])
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.notifyReadError(errors.WithStack(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) defaultMonitor() {
|
||||||
|
buf := make([]byte, mtuLimit)
|
||||||
|
for {
|
||||||
|
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
||||||
|
if n >= l.headerSize+IKCP_OVERHEAD {
|
||||||
|
l.packetInput(buf[:n], from)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l.notifyReadError(errors.WithStack(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
vendor/github.com/fatedier/kcp-go/readloop_generic.go
generated
vendored
Normal file
11
vendor/github.com/fatedier/kcp-go/readloop_generic.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package kcp
|
||||||
|
|
||||||
|
func (s *UDPSession) readLoop() {
|
||||||
|
s.defaultReadLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) monitor() {
|
||||||
|
l.defaultMonitor()
|
||||||
|
}
|
||||||
120
vendor/github.com/fatedier/kcp-go/readloop_linux.go
generated
vendored
Normal file
120
vendor/github.com/fatedier/kcp-go/readloop_linux.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// the read loop for a client session
|
||||||
|
func (s *UDPSession) readLoop() {
|
||||||
|
// default version
|
||||||
|
if s.xconn == nil {
|
||||||
|
s.defaultReadLoop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// x/net version
|
||||||
|
var src string
|
||||||
|
msgs := make([]ipv4.Message, batchSize)
|
||||||
|
for k := range msgs {
|
||||||
|
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if count, err := s.xconn.ReadBatch(msgs, 0); err == nil {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
msg := &msgs[i]
|
||||||
|
// make sure the packet is from the same source
|
||||||
|
if src == "" { // set source address if nil
|
||||||
|
src = msg.Addr.String()
|
||||||
|
} else if msg.Addr.String() != src {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.N < s.headerSize+IKCP_OVERHEAD {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// source and size has validated
|
||||||
|
s.packetInput(msg.Buffers[0][:msg.N])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// compatibility issue:
|
||||||
|
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||||
|
// an error of type os.SyscallError will be returned
|
||||||
|
if operr, ok := err.(*net.OpError); ok {
|
||||||
|
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||||
|
if se.Syscall == "recvmmsg" {
|
||||||
|
s.defaultReadLoop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.notifyReadError(errors.WithStack(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor incoming data for all connections of server
|
||||||
|
func (l *Listener) monitor() {
|
||||||
|
var xconn batchConn
|
||||||
|
if _, ok := l.conn.(*net.UDPConn); ok {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", l.conn.LocalAddr().String())
|
||||||
|
if err == nil {
|
||||||
|
if addr.IP.To4() != nil {
|
||||||
|
xconn = ipv4.NewPacketConn(l.conn)
|
||||||
|
} else {
|
||||||
|
xconn = ipv6.NewPacketConn(l.conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default version
|
||||||
|
if xconn == nil {
|
||||||
|
l.defaultMonitor()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// x/net version
|
||||||
|
msgs := make([]ipv4.Message, batchSize)
|
||||||
|
for k := range msgs {
|
||||||
|
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if count, err := xconn.ReadBatch(msgs, 0); err == nil {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
msg := &msgs[i]
|
||||||
|
if msg.N >= l.headerSize+IKCP_OVERHEAD {
|
||||||
|
l.packetInput(msg.Buffers[0][:msg.N], msg.Addr)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// compatibility issue:
|
||||||
|
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||||
|
// an error of type os.SyscallError will be returned
|
||||||
|
if operr, ok := err.(*net.OpError); ok {
|
||||||
|
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||||
|
if se.Syscall == "recvmmsg" {
|
||||||
|
l.defaultMonitor()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.notifyReadError(errors.WithStack(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
602
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
602
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
@@ -1,9 +1,17 @@
|
|||||||
|
// Package kcp-go is a Reliable-UDP library for golang.
|
||||||
|
//
|
||||||
|
// This library intents to provide a smooth, resilient, ordered,
|
||||||
|
// error-checked and anonymous delivery of streams over UDP packets.
|
||||||
|
//
|
||||||
|
// The interfaces of this package aims to be compatible with
|
||||||
|
// net.Conn in standard library, but offers powerful features for advanced users.
|
||||||
package kcp
|
package kcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -14,14 +22,6 @@ import (
|
|||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type errTimeout struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (errTimeout) Timeout() bool { return true }
|
|
||||||
func (errTimeout) Temporary() bool { return true }
|
|
||||||
func (errTimeout) Error() string { return "i/o timeout" }
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 16-bytes nonce for each packet
|
// 16-bytes nonce for each packet
|
||||||
nonceSize = 16
|
nonceSize = 16
|
||||||
@@ -42,9 +42,9 @@ const (
|
|||||||
acceptBacklog = 128
|
acceptBacklog = 128
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
errBrokenPipe = "broken pipe"
|
errInvalidOperation = errors.New("invalid operation")
|
||||||
errInvalidOperation = "invalid operation"
|
errTimeout = errors.New("timeout")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -72,8 +72,6 @@ type (
|
|||||||
// recvbuf turns packets into stream
|
// recvbuf turns packets into stream
|
||||||
recvbuf []byte
|
recvbuf []byte
|
||||||
bufptr []byte
|
bufptr []byte
|
||||||
// header extended output buffer, if has header
|
|
||||||
ext []byte
|
|
||||||
|
|
||||||
// FEC codec
|
// FEC codec
|
||||||
fecDecoder *fecDecoder
|
fecDecoder *fecDecoder
|
||||||
@@ -90,16 +88,27 @@ type (
|
|||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
die chan struct{} // notify current session has Closed
|
die chan struct{} // notify current session has Closed
|
||||||
|
dieOnce sync.Once
|
||||||
chReadEvent chan struct{} // notify Read() can be called without blocking
|
chReadEvent chan struct{} // notify Read() can be called without blocking
|
||||||
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
||||||
chReadError chan error // notify PacketConn.Read() have an error
|
|
||||||
chWriteError chan error // notify PacketConn.Write() have an error
|
// socket error handling
|
||||||
|
socketReadError atomic.Value
|
||||||
|
socketWriteError atomic.Value
|
||||||
|
chSocketReadError chan struct{}
|
||||||
|
chSocketWriteError chan struct{}
|
||||||
|
socketReadErrorOnce sync.Once
|
||||||
|
socketWriteErrorOnce sync.Once
|
||||||
|
|
||||||
// nonce generator
|
// nonce generator
|
||||||
nonce Entropy
|
nonce Entropy
|
||||||
|
|
||||||
isClosed bool // flag the session has Closed
|
// packets waiting to be sent on wire
|
||||||
mu sync.Mutex
|
txqueue []ipv4.Message
|
||||||
|
xconn batchConn // for x/net
|
||||||
|
xconnWriteError error
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadBuffer interface {
|
setReadBuffer interface {
|
||||||
@@ -119,14 +128,26 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
sess.nonce.Init()
|
sess.nonce.Init()
|
||||||
sess.chReadEvent = make(chan struct{}, 1)
|
sess.chReadEvent = make(chan struct{}, 1)
|
||||||
sess.chWriteEvent = make(chan struct{}, 1)
|
sess.chWriteEvent = make(chan struct{}, 1)
|
||||||
sess.chReadError = make(chan error, 1)
|
sess.chSocketReadError = make(chan struct{})
|
||||||
sess.chWriteError = make(chan error, 1)
|
sess.chSocketWriteError = make(chan struct{})
|
||||||
sess.remote = remote
|
sess.remote = remote
|
||||||
sess.conn = conn
|
sess.conn = conn
|
||||||
sess.l = l
|
sess.l = l
|
||||||
sess.block = block
|
sess.block = block
|
||||||
sess.recvbuf = make([]byte, mtuLimit)
|
sess.recvbuf = make([]byte, mtuLimit)
|
||||||
|
|
||||||
|
// cast to writebatch conn
|
||||||
|
if _, ok := conn.(*net.UDPConn); ok {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
|
||||||
|
if err == nil {
|
||||||
|
if addr.IP.To4() != nil {
|
||||||
|
sess.xconn = ipv4.NewPacketConn(conn)
|
||||||
|
} else {
|
||||||
|
sess.xconn = ipv6.NewPacketConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FEC codec initialization
|
// FEC codec initialization
|
||||||
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
||||||
if sess.block != nil {
|
if sess.block != nil {
|
||||||
@@ -143,17 +164,12 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
sess.headerSize += fecHeaderSizePlus2
|
sess.headerSize += fecHeaderSizePlus2
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only need to allocate extended packet buffer if we have the additional header
|
|
||||||
if sess.headerSize > 0 {
|
|
||||||
sess.ext = make([]byte, mtuLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
sess.kcp = NewKCP(conv, func(buf []byte, size int) {
|
sess.kcp = NewKCP(conv, func(buf []byte, size int) {
|
||||||
if size >= IKCP_OVERHEAD {
|
if size >= IKCP_OVERHEAD+sess.headerSize {
|
||||||
sess.output(buf[:size])
|
sess.output(buf[:size])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
sess.kcp.ReserveBytes(sess.headerSize)
|
||||||
|
|
||||||
// register current session to the global updater,
|
// register current session to the global updater,
|
||||||
// which call sess.update() periodically.
|
// which call sess.update() periodically.
|
||||||
@@ -165,6 +181,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
} else {
|
} else {
|
||||||
atomic.AddUint64(&DefaultSnmp.PassiveOpens, 1)
|
atomic.AddUint64(&DefaultSnmp.PassiveOpens, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
currestab := atomic.AddUint64(&DefaultSnmp.CurrEstab, 1)
|
currestab := atomic.AddUint64(&DefaultSnmp.CurrEstab, 1)
|
||||||
maxconn := atomic.LoadUint64(&DefaultSnmp.MaxConn)
|
maxconn := atomic.LoadUint64(&DefaultSnmp.MaxConn)
|
||||||
if currestab > maxconn {
|
if currestab > maxconn {
|
||||||
@@ -186,11 +203,6 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.isClosed {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return 0, errors.New(errBrokenPipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
||||||
if len(b) >= size { // receive data into 'b' directly
|
if len(b) >= size { // receive data into 'b' directly
|
||||||
s.kcp.Recv(b)
|
s.kcp.Recv(b)
|
||||||
@@ -220,7 +232,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
if !s.rd.IsZero() {
|
if !s.rd.IsZero() {
|
||||||
if time.Now().After(s.rd) {
|
if time.Now().After(s.rd) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return 0, errTimeout{}
|
return 0, errors.WithStack(errTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
delay := s.rd.Sub(time.Now())
|
delay := s.rd.Sub(time.Now())
|
||||||
@@ -229,63 +241,66 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
// wait for read event or timeout
|
// wait for read event or timeout or error
|
||||||
select {
|
select {
|
||||||
case <-s.chReadEvent:
|
case <-s.chReadEvent:
|
||||||
case <-c:
|
|
||||||
case <-s.die:
|
|
||||||
case err = <-s.chReadError:
|
|
||||||
if timeout != nil {
|
if timeout != nil {
|
||||||
timeout.Stop()
|
timeout.Stop()
|
||||||
}
|
}
|
||||||
return n, err
|
case <-c:
|
||||||
}
|
return 0, errors.WithStack(errTimeout)
|
||||||
|
case <-s.chSocketReadError:
|
||||||
if timeout != nil {
|
return 0, s.socketReadError.Load().(error)
|
||||||
timeout.Stop()
|
case <-s.die:
|
||||||
|
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements net.Conn
|
// Write implements net.Conn
|
||||||
func (s *UDPSession) Write(b []byte) (n int, err error) {
|
func (s *UDPSession) Write(b []byte) (n int, err error) { return s.WriteBuffers([][]byte{b}) }
|
||||||
|
|
||||||
|
// WriteBuffers write a vector of byte slices to the underlying connection
|
||||||
|
func (s *UDPSession) WriteBuffers(v [][]byte) (n int, err error) {
|
||||||
for {
|
for {
|
||||||
s.mu.Lock()
|
select {
|
||||||
if s.isClosed {
|
case <-s.chSocketWriteError:
|
||||||
s.mu.Unlock()
|
return 0, s.socketWriteError.Load().(error)
|
||||||
return 0, errors.New(errBrokenPipe)
|
case <-s.die:
|
||||||
|
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// controls how much data will be sent to kcp core
|
s.mu.Lock()
|
||||||
// to prevent the memory from exhuasting
|
|
||||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
||||||
n = len(b)
|
for _, b := range v {
|
||||||
for {
|
n += len(b)
|
||||||
if len(b) <= int(s.kcp.mss) {
|
for {
|
||||||
s.kcp.Send(b)
|
if len(b) <= int(s.kcp.mss) {
|
||||||
break
|
s.kcp.Send(b)
|
||||||
} else {
|
break
|
||||||
s.kcp.Send(b[:s.kcp.mss])
|
} else {
|
||||||
b = b[s.kcp.mss:]
|
s.kcp.Send(b[:s.kcp.mss])
|
||||||
|
b = b[s.kcp.mss:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush immediately if the queue is full
|
|
||||||
if s.kcp.WaitSnd() >= int(s.kcp.snd_wnd) || !s.writeDelay {
|
if s.kcp.WaitSnd() >= int(s.kcp.snd_wnd) || !s.writeDelay {
|
||||||
s.kcp.flush(false)
|
s.kcp.flush(false)
|
||||||
|
s.uncork()
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
atomic.AddUint64(&DefaultSnmp.BytesSent, uint64(n))
|
atomic.AddUint64(&DefaultSnmp.BytesSent, uint64(n))
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deadline for current writing operation
|
|
||||||
var timeout *time.Timer
|
var timeout *time.Timer
|
||||||
var c <-chan time.Time
|
var c <-chan time.Time
|
||||||
if !s.wd.IsZero() {
|
if !s.wd.IsZero() {
|
||||||
if time.Now().After(s.wd) {
|
if time.Now().After(s.wd) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return 0, errTimeout{}
|
return 0, errors.WithStack(errTimeout)
|
||||||
}
|
}
|
||||||
delay := s.wd.Sub(time.Now())
|
delay := s.wd.Sub(time.Now())
|
||||||
timeout = time.NewTimer(delay)
|
timeout = time.NewTimer(delay)
|
||||||
@@ -293,44 +308,52 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
// wait for write event or timeout
|
|
||||||
select {
|
select {
|
||||||
case <-s.chWriteEvent:
|
case <-s.chWriteEvent:
|
||||||
case <-c:
|
|
||||||
case <-s.die:
|
|
||||||
case err = <-s.chWriteError:
|
|
||||||
if timeout != nil {
|
if timeout != nil {
|
||||||
timeout.Stop()
|
timeout.Stop()
|
||||||
}
|
}
|
||||||
return n, err
|
case <-c:
|
||||||
}
|
return 0, errors.WithStack(errTimeout)
|
||||||
|
case <-s.chSocketWriteError:
|
||||||
if timeout != nil {
|
return 0, s.socketWriteError.Load().(error)
|
||||||
timeout.Stop()
|
case <-s.die:
|
||||||
|
return 0, errors.WithStack(io.ErrClosedPipe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// uncork sends data in txqueue if there is any
|
||||||
|
func (s *UDPSession) uncork() {
|
||||||
|
if len(s.txqueue) > 0 {
|
||||||
|
s.tx(s.txqueue)
|
||||||
|
s.txqueue = s.txqueue[:0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the connection.
|
// Close closes the connection.
|
||||||
func (s *UDPSession) Close() error {
|
func (s *UDPSession) Close() error {
|
||||||
// remove current session from updater & listener(if necessary)
|
var once bool
|
||||||
updater.removeSession(s)
|
s.dieOnce.Do(func() {
|
||||||
if s.l != nil { // notify listener
|
close(s.die)
|
||||||
s.l.closeSession(s.remote)
|
once = true
|
||||||
}
|
})
|
||||||
|
|
||||||
s.mu.Lock()
|
if once {
|
||||||
defer s.mu.Unlock()
|
// remove from updater
|
||||||
if s.isClosed {
|
updater.removeSession(s)
|
||||||
return errors.New(errBrokenPipe)
|
atomic.AddUint64(&DefaultSnmp.CurrEstab, ^uint64(0))
|
||||||
|
|
||||||
|
if s.l != nil { // belongs to listener
|
||||||
|
s.l.closeSession(s.remote)
|
||||||
|
return nil
|
||||||
|
} else { // client socket close
|
||||||
|
return s.conn.Close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.WithStack(io.ErrClosedPipe)
|
||||||
}
|
}
|
||||||
close(s.die)
|
|
||||||
s.isClosed = true
|
|
||||||
atomic.AddUint64(&DefaultSnmp.CurrEstab, ^uint64(0))
|
|
||||||
if s.l == nil { // client socket close
|
|
||||||
return s.conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
||||||
@@ -390,7 +413,7 @@ func (s *UDPSession) SetMtu(mtu int) bool {
|
|||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.kcp.SetMtu(mtu - s.headerSize)
|
s.kcp.SetMtu(mtu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +435,9 @@ func (s *UDPSession) SetACKNoDelay(nodelay bool) {
|
|||||||
s.ackNoDelay = nodelay
|
s.ackNoDelay = nodelay
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDUP duplicates udp packets for kcp output, for testing purpose only
|
// (deprecated)
|
||||||
|
//
|
||||||
|
// SetDUP duplicates udp packets for kcp output.
|
||||||
func (s *UDPSession) SetDUP(dup int) {
|
func (s *UDPSession) SetDUP(dup int) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@@ -427,19 +452,29 @@ func (s *UDPSession) SetNoDelay(nodelay, interval, resend, nc int) {
|
|||||||
s.kcp.NoDelay(nodelay, interval, resend, nc)
|
s.kcp.NoDelay(nodelay, interval, resend, nc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDSCP sets the 6bit DSCP field of IP header, no effect if it's accepted from Listener
|
// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||||
|
//
|
||||||
|
// It has no effect if it's accepted from Listener.
|
||||||
func (s *UDPSession) SetDSCP(dscp int) error {
|
func (s *UDPSession) SetDSCP(dscp int) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
if s.l == nil {
|
if s.l != nil {
|
||||||
if nc, ok := s.conn.(net.Conn); ok {
|
return errInvalidOperation
|
||||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
}
|
||||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
if nc, ok := s.conn.(net.Conn); ok {
|
||||||
}
|
var succeed bool
|
||||||
|
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err == nil {
|
||||||
|
succeed = true
|
||||||
|
}
|
||||||
|
if err := ipv6.NewConn(nc).SetTrafficClass(dscp); err == nil {
|
||||||
|
succeed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if succeed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetReadBuffer sets the socket read buffer, no effect if it's accepted from Listener
|
// SetReadBuffer sets the socket read buffer, no effect if it's accepted from Listener
|
||||||
@@ -451,7 +486,7 @@ func (s *UDPSession) SetReadBuffer(bytes int) error {
|
|||||||
return nc.SetReadBuffer(bytes)
|
return nc.SetReadBuffer(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteBuffer sets the socket write buffer, no effect if it's accepted from Listener
|
// SetWriteBuffer sets the socket write buffer, no effect if it's accepted from Listener
|
||||||
@@ -463,37 +498,29 @@ func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
|||||||
return nc.SetWriteBuffer(bytes)
|
return nc.SetWriteBuffer(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// post-processing for sending a packet from kcp core
|
// post-processing for sending a packet from kcp core
|
||||||
// steps:
|
// steps:
|
||||||
// 0. Header extending
|
|
||||||
// 1. FEC packet generation
|
// 1. FEC packet generation
|
||||||
// 2. CRC32 integrity
|
// 2. CRC32 integrity
|
||||||
// 3. Encryption
|
// 3. Encryption
|
||||||
// 4. WriteTo kernel
|
// 4. TxQueue
|
||||||
func (s *UDPSession) output(buf []byte) {
|
func (s *UDPSession) output(buf []byte) {
|
||||||
var ecc [][]byte
|
var ecc [][]byte
|
||||||
|
|
||||||
// 0. extend buf's header space(if necessary)
|
|
||||||
ext := buf
|
|
||||||
if s.headerSize > 0 {
|
|
||||||
ext = s.ext[:s.headerSize+len(buf)]
|
|
||||||
copy(ext[s.headerSize:], buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. FEC encoding
|
// 1. FEC encoding
|
||||||
if s.fecEncoder != nil {
|
if s.fecEncoder != nil {
|
||||||
ecc = s.fecEncoder.encode(ext)
|
ecc = s.fecEncoder.encode(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2&3. crc32 & encryption
|
// 2&3. crc32 & encryption
|
||||||
if s.block != nil {
|
if s.block != nil {
|
||||||
s.nonce.Fill(ext[:nonceSize])
|
s.nonce.Fill(buf[:nonceSize])
|
||||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
checksum := crc32.ChecksumIEEE(buf[cryptHeaderSize:])
|
||||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
binary.LittleEndian.PutUint32(buf[nonceSize:], checksum)
|
||||||
s.block.Encrypt(ext, ext)
|
s.block.Encrypt(buf, buf)
|
||||||
|
|
||||||
for k := range ecc {
|
for k := range ecc {
|
||||||
s.nonce.Fill(ecc[k][:nonceSize])
|
s.nonce.Fill(ecc[k][:nonceSize])
|
||||||
@@ -503,28 +530,23 @@ func (s *UDPSession) output(buf []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. WriteTo kernel
|
// 4. TxQueue
|
||||||
nbytes := 0
|
var msg ipv4.Message
|
||||||
npkts := 0
|
|
||||||
for i := 0; i < s.dup+1; i++ {
|
for i := 0; i < s.dup+1; i++ {
|
||||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
bts := xmitBuf.Get().([]byte)[:len(buf)]
|
||||||
nbytes += n
|
copy(bts, buf)
|
||||||
npkts++
|
msg.Buffers = [][]byte{bts}
|
||||||
} else {
|
msg.Addr = s.remote
|
||||||
s.notifyWriteError(err)
|
s.txqueue = append(s.txqueue, msg)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range ecc {
|
for k := range ecc {
|
||||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
bts := xmitBuf.Get().([]byte)[:len(ecc[k])]
|
||||||
nbytes += n
|
copy(bts, ecc[k])
|
||||||
npkts++
|
msg.Buffers = [][]byte{bts}
|
||||||
} else {
|
msg.Addr = s.remote
|
||||||
s.notifyWriteError(err)
|
s.txqueue = append(s.txqueue, msg)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
|
||||||
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// kcp update, returns interval for next calling
|
// kcp update, returns interval for next calling
|
||||||
@@ -535,6 +557,7 @@ func (s *UDPSession) update() (interval time.Duration) {
|
|||||||
if s.kcp.WaitSnd() < waitsnd {
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
s.notifyWriteEvent()
|
s.notifyWriteEvent()
|
||||||
}
|
}
|
||||||
|
s.uncork()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -556,10 +579,39 @@ func (s *UDPSession) notifyWriteEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UDPSession) notifyReadError(err error) {
|
||||||
|
s.socketReadErrorOnce.Do(func() {
|
||||||
|
s.socketReadError.Store(err)
|
||||||
|
close(s.chSocketReadError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UDPSession) notifyWriteError(err error) {
|
func (s *UDPSession) notifyWriteError(err error) {
|
||||||
select {
|
s.socketWriteErrorOnce.Do(func() {
|
||||||
case s.chWriteError <- err:
|
s.socketWriteError.Store(err)
|
||||||
default:
|
close(s.chSocketWriteError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// packet input stage
|
||||||
|
func (s *UDPSession) packetInput(data []byte) {
|
||||||
|
dataValid := false
|
||||||
|
if s.block != nil {
|
||||||
|
s.block.Decrypt(data, data)
|
||||||
|
data = data[nonceSize:]
|
||||||
|
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||||
|
if checksum == binary.LittleEndian.Uint32(data) {
|
||||||
|
data = data[crcSize:]
|
||||||
|
dataValid = true
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||||
|
}
|
||||||
|
} else if s.block == nil {
|
||||||
|
dataValid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataValid {
|
||||||
|
s.kcpInput(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,16 +620,16 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
|
|
||||||
if s.fecDecoder != nil {
|
if s.fecDecoder != nil {
|
||||||
if len(data) > fecHeaderSize { // must be larger than fec header size
|
if len(data) > fecHeaderSize { // must be larger than fec header size
|
||||||
f := s.fecDecoder.decodeBytes(data)
|
f := fecPacket(data)
|
||||||
if f.flag == typeData || f.flag == typeFEC { // header check
|
if f.flag() == typeData || f.flag() == typeParity { // header check
|
||||||
if f.flag == typeFEC {
|
if f.flag() == typeParity {
|
||||||
fecParityShards++
|
fecParityShards++
|
||||||
}
|
}
|
||||||
recovers := s.fecDecoder.decode(f)
|
recovers := s.fecDecoder.decode(f)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
waitsnd := s.kcp.WaitSnd()
|
waitsnd := s.kcp.WaitSnd()
|
||||||
if f.flag == typeData {
|
if f.flag() == typeData {
|
||||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
||||||
kcpInErrors++
|
kcpInErrors++
|
||||||
}
|
}
|
||||||
@@ -598,6 +650,8 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
} else {
|
} else {
|
||||||
fecErrs++
|
fecErrs++
|
||||||
}
|
}
|
||||||
|
// recycle the recovers
|
||||||
|
xmitBuf.Put(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// to notify the readers to receive the data
|
// to notify the readers to receive the data
|
||||||
@@ -608,6 +662,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
if s.kcp.WaitSnd() < waitsnd {
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
s.notifyWriteEvent()
|
s.notifyWriteEvent()
|
||||||
}
|
}
|
||||||
|
s.uncork()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
} else {
|
} else {
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
@@ -627,6 +682,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
if s.kcp.WaitSnd() < waitsnd {
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
s.notifyWriteEvent()
|
s.notifyWriteEvent()
|
||||||
}
|
}
|
||||||
|
s.uncork()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,50 +700,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
if fecRecovered > 0 {
|
if fecRecovered > 0 {
|
||||||
atomic.AddUint64(&DefaultSnmp.FECRecovered, fecRecovered)
|
atomic.AddUint64(&DefaultSnmp.FECRecovered, fecRecovered)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// the read loop for a client session
|
|
||||||
func (s *UDPSession) readLoop() {
|
|
||||||
buf := make([]byte, mtuLimit)
|
|
||||||
var src string
|
|
||||||
for {
|
|
||||||
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
|
||||||
// make sure the packet is from the same source
|
|
||||||
if src == "" { // set source address
|
|
||||||
src = addr.String()
|
|
||||||
} else if addr.String() != src {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if n >= s.headerSize+IKCP_OVERHEAD {
|
|
||||||
data := buf[:n]
|
|
||||||
dataValid := false
|
|
||||||
if s.block != nil {
|
|
||||||
s.block.Decrypt(data, data)
|
|
||||||
data = data[nonceSize:]
|
|
||||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
|
||||||
if checksum == binary.LittleEndian.Uint32(data) {
|
|
||||||
data = data[crcSize:]
|
|
||||||
dataValid = true
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
|
||||||
}
|
|
||||||
} else if s.block == nil {
|
|
||||||
dataValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataValid {
|
|
||||||
s.kcpInput(data)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.chReadError <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -704,98 +717,91 @@ type (
|
|||||||
chAccepts chan *UDPSession // Listen() backlog
|
chAccepts chan *UDPSession // Listen() backlog
|
||||||
chSessionClosed chan net.Addr // session close queue
|
chSessionClosed chan net.Addr // session close queue
|
||||||
headerSize int // the additional header to a KCP frame
|
headerSize int // the additional header to a KCP frame
|
||||||
die chan struct{} // notify the listener has closed
|
|
||||||
rd atomic.Value // read deadline for Accept()
|
die chan struct{} // notify the listener has closed
|
||||||
wd atomic.Value
|
dieOnce sync.Once
|
||||||
|
|
||||||
|
// socket error handling
|
||||||
|
socketReadError atomic.Value
|
||||||
|
chSocketReadError chan struct{}
|
||||||
|
socketReadErrorOnce sync.Once
|
||||||
|
|
||||||
|
rd atomic.Value // read deadline for Accept()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// monitor incoming data for all connections of server
|
// packet input stage
|
||||||
func (l *Listener) monitor() {
|
func (l *Listener) packetInput(data []byte, addr net.Addr) {
|
||||||
// a cache for session object last used
|
dataValid := false
|
||||||
var lastAddr string
|
if l.block != nil {
|
||||||
var lastSession *UDPSession
|
l.block.Decrypt(data, data)
|
||||||
buf := make([]byte, mtuLimit)
|
data = data[nonceSize:]
|
||||||
for {
|
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||||
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
if checksum == binary.LittleEndian.Uint32(data) {
|
||||||
if n >= l.headerSize+IKCP_OVERHEAD {
|
data = data[crcSize:]
|
||||||
data := buf[:n]
|
dataValid = true
|
||||||
dataValid := false
|
} else {
|
||||||
if l.block != nil {
|
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||||
l.block.Decrypt(data, data)
|
}
|
||||||
data = data[nonceSize:]
|
} else if l.block == nil {
|
||||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
dataValid = true
|
||||||
if checksum == binary.LittleEndian.Uint32(data) {
|
}
|
||||||
data = data[crcSize:]
|
|
||||||
dataValid = true
|
if dataValid {
|
||||||
} else {
|
l.sessionLock.Lock()
|
||||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
s, ok := l.sessions[addr.String()]
|
||||||
|
l.sessionLock.Unlock()
|
||||||
|
|
||||||
|
if !ok { // new address:port
|
||||||
|
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
||||||
|
var conv uint32
|
||||||
|
convValid := false
|
||||||
|
if l.fecDecoder != nil {
|
||||||
|
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||||
|
if isfec == typeData {
|
||||||
|
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||||
|
convValid = true
|
||||||
}
|
}
|
||||||
} else if l.block == nil {
|
} else {
|
||||||
dataValid = true
|
conv = binary.LittleEndian.Uint32(data)
|
||||||
|
convValid = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataValid {
|
if convValid { // creates a new session only if the 'conv' field in kcp is accessible
|
||||||
addr := from.String()
|
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, addr, l.block)
|
||||||
var s *UDPSession
|
s.kcpInput(data)
|
||||||
var ok bool
|
l.sessionLock.Lock()
|
||||||
|
l.sessions[addr.String()] = s
|
||||||
// the packets received from an address always come in batch,
|
l.sessionLock.Unlock()
|
||||||
// cache the session for next packet, without querying map.
|
l.chAccepts <- s
|
||||||
if addr == lastAddr {
|
|
||||||
s, ok = lastSession, true
|
|
||||||
} else {
|
|
||||||
l.sessionLock.Lock()
|
|
||||||
if s, ok = l.sessions[addr]; ok {
|
|
||||||
lastSession = s
|
|
||||||
lastAddr = addr
|
|
||||||
}
|
|
||||||
l.sessionLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok { // new session
|
|
||||||
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
|
||||||
var conv uint32
|
|
||||||
convValid := false
|
|
||||||
if l.fecDecoder != nil {
|
|
||||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
|
||||||
if isfec == typeData {
|
|
||||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
|
||||||
convValid = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conv = binary.LittleEndian.Uint32(data)
|
|
||||||
convValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if convValid { // creates a new session only if the 'conv' field in kcp is accessible
|
|
||||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
|
||||||
s.kcpInput(data)
|
|
||||||
l.sessionLock.Lock()
|
|
||||||
l.sessions[addr] = s
|
|
||||||
l.sessionLock.Unlock()
|
|
||||||
l.chAccepts <- s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.kcpInput(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return
|
s.kcpInput(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Listener) notifyReadError(err error) {
|
||||||
|
l.socketReadErrorOnce.Do(func() {
|
||||||
|
l.socketReadError.Store(err)
|
||||||
|
close(l.chSocketReadError)
|
||||||
|
|
||||||
|
// propagate read error to all sessions
|
||||||
|
l.sessionLock.Lock()
|
||||||
|
for _, s := range l.sessions {
|
||||||
|
s.notifyReadError(err)
|
||||||
|
}
|
||||||
|
l.sessionLock.Unlock()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetReadBuffer sets the socket read buffer for the Listener
|
// SetReadBuffer sets the socket read buffer for the Listener
|
||||||
func (l *Listener) SetReadBuffer(bytes int) error {
|
func (l *Listener) SetReadBuffer(bytes int) error {
|
||||||
if nc, ok := l.conn.(setReadBuffer); ok {
|
if nc, ok := l.conn.(setReadBuffer); ok {
|
||||||
return nc.SetReadBuffer(bytes)
|
return nc.SetReadBuffer(bytes)
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteBuffer sets the socket write buffer for the Listener
|
// SetWriteBuffer sets the socket write buffer for the Listener
|
||||||
@@ -803,18 +809,25 @@ func (l *Listener) SetWriteBuffer(bytes int) error {
|
|||||||
if nc, ok := l.conn.(setWriteBuffer); ok {
|
if nc, ok := l.conn.(setWriteBuffer); ok {
|
||||||
return nc.SetWriteBuffer(bytes)
|
return nc.SetWriteBuffer(bytes)
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDSCP sets the 6bit DSCP field of IP header
|
// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||||
func (l *Listener) SetDSCP(dscp int) error {
|
func (l *Listener) SetDSCP(dscp int) error {
|
||||||
if nc, ok := l.conn.(net.Conn); ok {
|
if nc, ok := l.conn.(net.Conn); ok {
|
||||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
var succeed bool
|
||||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err == nil {
|
||||||
|
succeed = true
|
||||||
|
}
|
||||||
|
if err := ipv6.NewConn(nc).SetTrafficClass(dscp); err == nil {
|
||||||
|
succeed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if succeed {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errInvalidOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
||||||
@@ -831,11 +844,13 @@ func (l *Listener) AcceptKCP() (*UDPSession, error) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
return nil, &errTimeout{}
|
return nil, errors.WithStack(errTimeout)
|
||||||
case c := <-l.chAccepts:
|
case c := <-l.chAccepts:
|
||||||
return c, nil
|
return c, nil
|
||||||
|
case <-l.chSocketReadError:
|
||||||
|
return nil, l.socketReadError.Load().(error)
|
||||||
case <-l.die:
|
case <-l.die:
|
||||||
return nil, errors.New(errBrokenPipe)
|
return nil, errors.WithStack(io.ErrClosedPipe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,15 +868,21 @@ func (l *Listener) SetReadDeadline(t time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
||||||
func (l *Listener) SetWriteDeadline(t time.Time) error {
|
func (l *Listener) SetWriteDeadline(t time.Time) error { return errInvalidOperation }
|
||||||
l.wd.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops listening on the UDP address. Already Accepted connections are not closed.
|
// Close stops listening on the UDP address, and closes the socket
|
||||||
func (l *Listener) Close() error {
|
func (l *Listener) Close() error {
|
||||||
close(l.die)
|
var once bool
|
||||||
return l.conn.Close()
|
l.dieOnce.Do(func() {
|
||||||
|
close(l.die)
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if once {
|
||||||
|
return l.conn.Close()
|
||||||
|
} else {
|
||||||
|
return errors.WithStack(io.ErrClosedPipe)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeSession notify the listener that a session has closed
|
// closeSession notify the listener that a session has closed
|
||||||
@@ -881,16 +902,21 @@ func (l *Listener) Addr() net.Addr { return l.conn.LocalAddr() }
|
|||||||
// Listen listens for incoming KCP packets addressed to the local address laddr on the network "udp",
|
// Listen listens for incoming KCP packets addressed to the local address laddr on the network "udp",
|
||||||
func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr, nil, 0, 0) }
|
func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr, nil, 0, 0) }
|
||||||
|
|
||||||
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption,
|
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption.
|
||||||
// dataShards, parityShards defines Reed-Solomon Erasure Coding parameters
|
//
|
||||||
|
// 'block' is the block encryption algorithm to encrypt packets.
|
||||||
|
//
|
||||||
|
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
|
||||||
|
//
|
||||||
|
// Check https://github.com/klauspost/reedsolomon for details
|
||||||
func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards int) (*Listener, error) {
|
func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards int) (*Listener, error) {
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", laddr)
|
udpaddr, err := net.ResolveUDPAddr("udp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
conn, err := net.ListenUDP("udp", udpaddr)
|
conn, err := net.ListenUDP("udp", udpaddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.ListenUDP")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ServeConn(block, dataShards, parityShards, conn)
|
return ServeConn(block, dataShards, parityShards, conn)
|
||||||
@@ -908,6 +934,7 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
|||||||
l.parityShards = parityShards
|
l.parityShards = parityShards
|
||||||
l.block = block
|
l.block = block
|
||||||
l.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
l.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
||||||
|
l.chSocketReadError = make(chan struct{})
|
||||||
|
|
||||||
// calculate header size
|
// calculate header size
|
||||||
if l.block != nil {
|
if l.block != nil {
|
||||||
@@ -921,15 +948,21 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
|||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the remote address "raddr" on the network "udp"
|
// Dial connects to the remote address "raddr" on the network "udp" without encryption and FEC
|
||||||
func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0, 0) }
|
func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0, 0) }
|
||||||
|
|
||||||
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
||||||
|
//
|
||||||
|
// 'block' is the block encryption algorithm to encrypt packets.
|
||||||
|
//
|
||||||
|
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
|
||||||
|
//
|
||||||
|
// Check https://github.com/klauspost/reedsolomon for details
|
||||||
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
||||||
// network type detection
|
// network type detection
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
network := "udp4"
|
network := "udp4"
|
||||||
if udpaddr.IP.To4() == nil {
|
if udpaddr.IP.To4() == nil {
|
||||||
@@ -938,30 +971,33 @@ func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards in
|
|||||||
|
|
||||||
conn, err := net.ListenUDP(network, nil)
|
conn, err := net.ListenUDP(network, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.DialUDP")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(raddr, block, dataShards, parityShards, conn)
|
return NewConn(raddr, block, dataShards, parityShards, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConn3 establishes a session and talks KCP protocol over a packet connection.
|
||||||
|
func NewConn3(convid uint32, raddr net.Addr, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||||
|
return newUDPSession(convid, dataShards, parityShards, nil, conn, raddr, block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn2 establishes a session and talks KCP protocol over a packet connection.
|
||||||
|
func NewConn2(raddr net.Addr, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||||
|
var convid uint32
|
||||||
|
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
||||||
|
return NewConn3(convid, raddr, block, dataShards, parityShards, conn)
|
||||||
|
}
|
||||||
|
|
||||||
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
||||||
func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*UDPSession, error) {
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
return NewConn2(udpaddr, block, dataShards, parityShards, conn)
|
||||||
var convid uint32
|
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
|
||||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// monotonic reference time point
|
|
||||||
var refTime time.Time = time.Now()
|
|
||||||
|
|
||||||
// currentMs returns current elasped monotonic milliseconds since program startup
|
|
||||||
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
|
|
||||||
|
|
||||||
func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, dataShards, parityShards int, conn *net.UDPConn) (*UDPSession, error) {
|
func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, dataShards, parityShards int, conn *net.UDPConn) (*UDPSession, error) {
|
||||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
25
vendor/github.com/fatedier/kcp-go/tx.go
generated
vendored
Normal file
25
vendor/github.com/fatedier/kcp-go/tx.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *UDPSession) defaultTx(txqueue []ipv4.Message) {
|
||||||
|
nbytes := 0
|
||||||
|
npkts := 0
|
||||||
|
for k := range txqueue {
|
||||||
|
if n, err := s.conn.WriteTo(txqueue[k].Buffers[0], txqueue[k].Addr); err == nil {
|
||||||
|
nbytes += n
|
||||||
|
npkts++
|
||||||
|
xmitBuf.Put(txqueue[k].Buffers[0])
|
||||||
|
} else {
|
||||||
|
s.notifyWriteError(errors.WithStack(err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||||
|
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
||||||
|
}
|
||||||
11
vendor/github.com/fatedier/kcp-go/tx_generic.go
generated
vendored
Normal file
11
vendor/github.com/fatedier/kcp-go/tx_generic.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *UDPSession) tx(txqueue []ipv4.Message) {
|
||||||
|
s.defaultTx(txqueue)
|
||||||
|
}
|
||||||
52
vendor/github.com/fatedier/kcp-go/tx_linux.go
generated
vendored
Normal file
52
vendor/github.com/fatedier/kcp-go/tx_linux.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *UDPSession) tx(txqueue []ipv4.Message) {
|
||||||
|
// default version
|
||||||
|
if s.xconn == nil || s.xconnWriteError != nil {
|
||||||
|
s.defaultTx(txqueue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// x/net version
|
||||||
|
nbytes := 0
|
||||||
|
npkts := 0
|
||||||
|
for len(txqueue) > 0 {
|
||||||
|
if n, err := s.xconn.WriteBatch(txqueue, 0); err == nil {
|
||||||
|
for k := range txqueue[:n] {
|
||||||
|
nbytes += len(txqueue[k].Buffers[0])
|
||||||
|
xmitBuf.Put(txqueue[k].Buffers[0])
|
||||||
|
}
|
||||||
|
npkts += n
|
||||||
|
txqueue = txqueue[n:]
|
||||||
|
} else {
|
||||||
|
// compatibility issue:
|
||||||
|
// for linux kernel<=2.6.32, support for sendmmsg is not available
|
||||||
|
// an error of type os.SyscallError will be returned
|
||||||
|
if operr, ok := err.(*net.OpError); ok {
|
||||||
|
if se, ok := operr.Err.(*os.SyscallError); ok {
|
||||||
|
if se.Syscall == "sendmmsg" {
|
||||||
|
s.xconnWriteError = se
|
||||||
|
s.defaultTx(txqueue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.notifyWriteError(errors.WithStack(err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||||
|
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
|
||||||
|
}
|
||||||
8
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
8
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
@@ -76,10 +76,10 @@ func (h *updateHeap) wakeup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *updateHeap) updateTask() {
|
func (h *updateHeap) updateTask() {
|
||||||
var timer <-chan time.Time
|
timer := time.NewTimer(0)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer:
|
case <-timer.C:
|
||||||
case <-h.chWakeUp:
|
case <-h.chWakeUp:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func (h *updateHeap) updateTask() {
|
|||||||
hlen := h.Len()
|
hlen := h.Len()
|
||||||
for i := 0; i < hlen; i++ {
|
for i := 0; i < hlen; i++ {
|
||||||
entry := &h.entries[0]
|
entry := &h.entries[0]
|
||||||
if time.Now().After(entry.ts) {
|
if !time.Now().Before(entry.ts) {
|
||||||
interval := entry.s.update()
|
interval := entry.s.update()
|
||||||
entry.ts = time.Now().Add(interval)
|
entry.ts = time.Now().Add(interval)
|
||||||
heap.Fix(h, 0)
|
heap.Fix(h, 0)
|
||||||
@@ -97,7 +97,7 @@ func (h *updateHeap) updateTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hlen > 0 {
|
if hlen > 0 {
|
||||||
timer = time.After(h.entries[0].ts.Sub(time.Now()))
|
timer.Reset(h.entries[0].ts.Sub(time.Now()))
|
||||||
}
|
}
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- go test -v -race ./...
|
|
||||||
10
vendor/github.com/gorilla/context/README.md
generated
vendored
10
vendor/github.com/gorilla/context/README.md
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
context
|
|
||||||
=======
|
|
||||||
[](https://travis-ci.org/gorilla/context)
|
|
||||||
|
|
||||||
gorilla/context is a general purpose registry for global request variables.
|
|
||||||
|
|
||||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
|
||||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
|
||||||
143
vendor/github.com/gorilla/context/context.go
generated
vendored
143
vendor/github.com/gorilla/context/context.go
generated
vendored
@@ -1,143 +0,0 @@
|
|||||||
// Copyright 2012 The Gorilla 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mutex sync.RWMutex
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set stores a value for a given key in a given request.
|
|
||||||
func Set(r *http.Request, key, val interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] == nil {
|
|
||||||
data[r] = make(map[interface{}]interface{})
|
|
||||||
datat[r] = time.Now().Unix()
|
|
||||||
}
|
|
||||||
data[r][key] = val
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a value stored for a given key in a given request.
|
|
||||||
func Get(r *http.Request, key interface{}) interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if ctx := data[r]; ctx != nil {
|
|
||||||
value := ctx[key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
|
||||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
if _, ok := data[r]; ok {
|
|
||||||
value, ok := data[r][key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value, ok
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
|
||||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if context, ok := data[r]; ok {
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
|
||||||
// the request was registered.
|
|
||||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
context, ok := data[r]
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a value stored for a given key in a given request.
|
|
||||||
func Delete(r *http.Request, key interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] != nil {
|
|
||||||
delete(data[r], key)
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all values stored for a given request.
|
|
||||||
//
|
|
||||||
// This is usually called by a handler wrapper to clean up request
|
|
||||||
// variables at the end of a request lifetime. See ClearHandler().
|
|
||||||
func Clear(r *http.Request) {
|
|
||||||
mutex.Lock()
|
|
||||||
clear(r)
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear is Clear without the lock.
|
|
||||||
func clear(r *http.Request) {
|
|
||||||
delete(data, r)
|
|
||||||
delete(datat, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
|
||||||
// It returns the amount of requests removed.
|
|
||||||
//
|
|
||||||
// If maxAge <= 0, all request data is removed.
|
|
||||||
//
|
|
||||||
// This is only used for sanity check: in case context cleaning was not
|
|
||||||
// properly set some request data can be kept forever, consuming an increasing
|
|
||||||
// amount of memory. In case this is detected, Purge() must be called
|
|
||||||
// periodically until the problem is fixed.
|
|
||||||
func Purge(maxAge int) int {
|
|
||||||
mutex.Lock()
|
|
||||||
count := 0
|
|
||||||
if maxAge <= 0 {
|
|
||||||
count = len(data)
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
} else {
|
|
||||||
min := time.Now().Unix() - int64(maxAge)
|
|
||||||
for r := range data {
|
|
||||||
if datat[r] < min {
|
|
||||||
clear(r)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
|
||||||
// of a request lifetime.
|
|
||||||
func ClearHandler(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer Clear(r)
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
@@ -1,88 +0,0 @@
|
|||||||
// Copyright 2012 The Gorilla 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 stores values shared during a request lifetime.
|
|
||||||
|
|
||||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
|
||||||
does not play well > with the shallow copying of the request that
|
|
||||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
|
||||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
|
||||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
For example, a router can set variables extracted from the URL and later
|
|
||||||
application handlers can access those values, or it can be used to store
|
|
||||||
sessions values to be saved at the end of a request. There are several
|
|
||||||
others common uses.
|
|
||||||
|
|
||||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
|
||||||
|
|
||||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
|
||||||
|
|
||||||
Here's the basic usage: first define the keys that you will need. The key
|
|
||||||
type is interface{} so a key can be of any type that supports equality.
|
|
||||||
Here we define a key using a custom int type to avoid name collisions:
|
|
||||||
|
|
||||||
package foo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const MyKey key = 0
|
|
||||||
|
|
||||||
Then set a variable. Variables are bound to an http.Request object, so you
|
|
||||||
need a request instance to set a value:
|
|
||||||
|
|
||||||
context.Set(r, MyKey, "bar")
|
|
||||||
|
|
||||||
The application can later access the variable using the same key you provided:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// val is "bar".
|
|
||||||
val := context.Get(r, foo.MyKey)
|
|
||||||
|
|
||||||
// returns ("bar", true)
|
|
||||||
val, ok := context.GetOk(r, foo.MyKey)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
And that's all about the basic usage. We discuss some other ideas below.
|
|
||||||
|
|
||||||
Any type can be stored in the context. To enforce a given type, make the key
|
|
||||||
private and wrap Get() and Set() to accept and return values of a specific
|
|
||||||
type:
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const mykey key = 0
|
|
||||||
|
|
||||||
// GetMyKey returns a value for this package from the request values.
|
|
||||||
func GetMyKey(r *http.Request) SomeType {
|
|
||||||
if rv := context.Get(r, mykey); rv != nil {
|
|
||||||
return rv.(SomeType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMyKey sets a value for this package in the request values.
|
|
||||||
func SetMyKey(r *http.Request, val SomeType) {
|
|
||||||
context.Set(r, mykey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
Variables must be cleared at the end of a request, to remove all values
|
|
||||||
that were stored. This can be done in an http.Handler, after a request was
|
|
||||||
served. Just call Clear() passing the request:
|
|
||||||
|
|
||||||
context.Clear(r)
|
|
||||||
|
|
||||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
|
||||||
variables at the end of a request lifetime.
|
|
||||||
|
|
||||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
|
||||||
so if you are using either of them you don't need to clear the context manually.
|
|
||||||
*/
|
|
||||||
package context
|
|
||||||
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.5.x
|
|
||||||
- go: 1.6.x
|
|
||||||
- go: 1.7.x
|
|
||||||
- go: 1.8.x
|
|
||||||
- go: 1.9.x
|
|
||||||
- go: 1.10.x
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- # Skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
||||||
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# This is the official list of gorilla/mux authors for copyright purposes.
|
||||||
|
#
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Google LLC (https://opensource.google.com/)
|
||||||
|
Kamil Kisielk <kamil@kamilkisiel.net>
|
||||||
|
Matt Silverlock <matt@eatsleeprepeat.net>
|
||||||
|
Rodrigo Moraes (https://github.com/moraes)
|
||||||
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
**What version of Go are you running?** (Paste the output of `go version`)
|
|
||||||
|
|
||||||
|
|
||||||
**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
|
|
||||||
|
|
||||||
|
|
||||||
**Describe your problem** (and what you have tried so far)
|
|
||||||
|
|
||||||
|
|
||||||
**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
|
|
||||||
|
|
||||||
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|||||||
85
vendor/github.com/gorilla/mux/README.md
generated
vendored
85
vendor/github.com/gorilla/mux/README.md
generated
vendored
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
[](https://godoc.org/github.com/gorilla/mux)
|
[](https://godoc.org/github.com/gorilla/mux)
|
||||||
[](https://travis-ci.org/gorilla/mux)
|
[](https://travis-ci.org/gorilla/mux)
|
||||||
|
[](https://circleci.com/gh/gorilla/mux)
|
||||||
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
http://www.gorillatoolkit.org/pkg/mux
|
https://www.gorillatoolkit.org/pkg/mux
|
||||||
|
|
||||||
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||||
their respective handler.
|
their respective handler.
|
||||||
@@ -29,6 +30,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
|
|||||||
* [Walking Routes](#walking-routes)
|
* [Walking Routes](#walking-routes)
|
||||||
* [Graceful Shutdown](#graceful-shutdown)
|
* [Graceful Shutdown](#graceful-shutdown)
|
||||||
* [Middleware](#middleware)
|
* [Middleware](#middleware)
|
||||||
|
* [Handling CORS Requests](#handling-cors-requests)
|
||||||
* [Testing Handlers](#testing-handlers)
|
* [Testing Handlers](#testing-handlers)
|
||||||
* [Full Example](#full-example)
|
* [Full Example](#full-example)
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ r := mux.NewRouter()
|
|||||||
// Only matches if domain is "www.example.com".
|
// Only matches if domain is "www.example.com".
|
||||||
r.Host("www.example.com")
|
r.Host("www.example.com")
|
||||||
// Matches a dynamic subdomain.
|
// Matches a dynamic subdomain.
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
r.Host("{subdomain:[a-z]+}.example.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
There are several other matchers that can be added. To match path prefixes:
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
@@ -238,13 +240,13 @@ This also works for host and query value variables:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.Host("{subdomain}.domain.com").
|
r.Host("{subdomain}.example.com").
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
Queries("filter", "{filter}").
|
Queries("filter", "{filter}").
|
||||||
HandlerFunc(ArticleHandler).
|
HandlerFunc(ArticleHandler).
|
||||||
Name("article")
|
Name("article")
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
"category", "technology",
|
"category", "technology",
|
||||||
"id", "42",
|
"id", "42",
|
||||||
@@ -264,7 +266,7 @@ r.HeadersRegexp("Content-Type", "application/(text|json)")
|
|||||||
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// "http://news.domain.com/"
|
// "http://news.example.com/"
|
||||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
// "/articles/technology/42"
|
// "/articles/technology/42"
|
||||||
@@ -275,12 +277,12 @@ And if you use subrouters, host and path defined separately can be built as well
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
s := r.Host("{subdomain}.example.com").Subrouter()
|
||||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
HandlerFunc(ArticleHandler).
|
HandlerFunc(ArticleHandler).
|
||||||
Name("article")
|
Name("article")
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
// "http://news.example.com/articles/technology/42"
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
"category", "technology",
|
"category", "technology",
|
||||||
"id", "42")
|
"id", "42")
|
||||||
@@ -491,6 +493,73 @@ r.Use(amw.Middleware)
|
|||||||
|
|
||||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
||||||
|
|
||||||
|
### Handling CORS Requests
|
||||||
|
|
||||||
|
[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
|
||||||
|
|
||||||
|
* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
|
||||||
|
* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
|
||||||
|
* If you do not specify any methods, then:
|
||||||
|
> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
|
||||||
|
|
||||||
|
Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
|
||||||
|
r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
|
||||||
|
r.Use(mux.CORSMethodMiddleware(r))
|
||||||
|
|
||||||
|
http.ListenAndServe(":8080", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fooHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("foo"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And an request to `/foo` using something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl localhost:8080/foo -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Would look like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
* Trying ::1...
|
||||||
|
* TCP_NODELAY set
|
||||||
|
* Connected to localhost (::1) port 8080 (#0)
|
||||||
|
> GET /foo HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.59.0
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
|
||||||
|
< Access-Control-Allow-Origin: *
|
||||||
|
< Date: Fri, 28 Jun 2019 20:13:30 GMT
|
||||||
|
< Content-Length: 3
|
||||||
|
< Content-Type: text/plain; charset=utf-8
|
||||||
|
<
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
foo
|
||||||
|
```
|
||||||
|
|
||||||
### Testing Handlers
|
### Testing Handlers
|
||||||
|
|
||||||
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
||||||
@@ -503,8 +572,8 @@ package main
|
|||||||
|
|
||||||
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// A very simple health check.
|
// A very simple health check.
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
// In the future we could report back on the status of our DB, or our cache
|
// In the future we could report back on the status of our DB, or our cache
|
||||||
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// +build go1.7
|
|
||||||
|
|
||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -18,7 +16,3 @@ func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|||||||
|
|
||||||
return r.WithContext(context.WithValue(r.Context(), key, val))
|
return r.WithContext(context.WithValue(r.Context(), key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
|
||||||
return context.Get(r, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|
||||||
if val == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Set(r, key, val)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
context.Clear(r)
|
|
||||||
}
|
|
||||||
2
vendor/github.com/gorilla/mux/doc.go
generated
vendored
2
vendor/github.com/gorilla/mux/doc.go
generated
vendored
@@ -295,7 +295,7 @@ A more complex authentication middleware, which maps session token to users, cou
|
|||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/", handler)
|
r.HandleFunc("/", handler)
|
||||||
|
|
||||||
amw := authenticationMiddleware{}
|
amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
|
||||||
amw.Populate()
|
amw.Populate()
|
||||||
|
|
||||||
r.Use(amw.Middleware)
|
r.Use(amw.Middleware)
|
||||||
|
|||||||
1
vendor/github.com/gorilla/mux/go.mod
generated
vendored
Normal file
1
vendor/github.com/gorilla/mux/go.mod
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module github.com/gorilla/mux
|
||||||
61
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
61
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
@@ -32,37 +32,19 @@ func (r *Router) useInterface(mw middleware) {
|
|||||||
r.middlewares = append(r.middlewares, mw)
|
r.middlewares = append(r.middlewares, mw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
|
// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
|
||||||
// on a request, by matching routes based only on paths. It also handles
|
// on requests for routes that have an OPTIONS method matcher to all the method matchers on
|
||||||
// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
|
// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
|
||||||
// returning without calling the next http handler.
|
// by the middleware. See examples for usage.
|
||||||
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
var allMethods []string
|
allMethods, err := getAllMethodsForRoute(r, req)
|
||||||
|
|
||||||
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
|
||||||
for _, m := range route.matchers {
|
|
||||||
if _, ok := m.(*routeRegexp); ok {
|
|
||||||
if m.Match(req, &RouteMatch{}) {
|
|
||||||
methods, err := route.GetMethods()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
allMethods = append(allMethods, methods...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
|
for _, v := range allMethods {
|
||||||
|
if v == http.MethodOptions {
|
||||||
if req.Method == "OPTIONS" {
|
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,3 +52,28 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getAllMethodsForRoute returns all the methods from method matchers matching a given
|
||||||
|
// request.
|
||||||
|
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
|
||||||
|
var allMethods []string
|
||||||
|
|
||||||
|
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
||||||
|
for _, m := range route.matchers {
|
||||||
|
if _, ok := m.(*routeRegexp); ok {
|
||||||
|
if m.Match(req, &RouteMatch{}) {
|
||||||
|
methods, err := route.GetMethods()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
allMethods = append(allMethods, methods...)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return allMethods, err
|
||||||
|
}
|
||||||
|
|||||||
129
vendor/github.com/gorilla/mux/mux.go
generated
vendored
129
vendor/github.com/gorilla/mux/mux.go
generated
vendored
@@ -22,7 +22,7 @@ var (
|
|||||||
|
|
||||||
// NewRouter returns a new router instance.
|
// NewRouter returns a new router instance.
|
||||||
func NewRouter() *Router {
|
func NewRouter() *Router {
|
||||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
return &Router{namedRoutes: make(map[string]*Route)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router registers routes to be matched and dispatches a handler.
|
// Router registers routes to be matched and dispatches a handler.
|
||||||
@@ -50,24 +50,78 @@ type Router struct {
|
|||||||
// Configurable Handler to be used when the request method does not match the route.
|
// Configurable Handler to be used when the request method does not match the route.
|
||||||
MethodNotAllowedHandler http.Handler
|
MethodNotAllowedHandler http.Handler
|
||||||
|
|
||||||
// Parent route, if this is a subrouter.
|
|
||||||
parent parentRoute
|
|
||||||
// Routes to be matched, in order.
|
// Routes to be matched, in order.
|
||||||
routes []*Route
|
routes []*Route
|
||||||
|
|
||||||
// Routes by name for URL building.
|
// Routes by name for URL building.
|
||||||
namedRoutes map[string]*Route
|
namedRoutes map[string]*Route
|
||||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
|
||||||
strictSlash bool
|
|
||||||
// See Router.SkipClean(). This defines the flag for new routes.
|
|
||||||
skipClean bool
|
|
||||||
// If true, do not clear the request context after handling the request.
|
// If true, do not clear the request context after handling the request.
|
||||||
// This has no effect when go1.7+ is used, since the context is stored
|
//
|
||||||
|
// Deprecated: No effect when go1.7+ is used, since the context is stored
|
||||||
// on the request itself.
|
// on the request itself.
|
||||||
KeepContext bool
|
KeepContext bool
|
||||||
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
|
||||||
useEncodedPath bool
|
|
||||||
// Slice of middlewares to be called after a match is found
|
// Slice of middlewares to be called after a match is found
|
||||||
middlewares []middleware
|
middlewares []middleware
|
||||||
|
|
||||||
|
// configuration shared with `Route`
|
||||||
|
routeConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// common route configuration shared between `Router` and `Route`
|
||||||
|
type routeConf struct {
|
||||||
|
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||||
|
useEncodedPath bool
|
||||||
|
|
||||||
|
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||||
|
// redirect to the former and vice versa.
|
||||||
|
strictSlash bool
|
||||||
|
|
||||||
|
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||||
|
// will not redirect
|
||||||
|
skipClean bool
|
||||||
|
|
||||||
|
// Manager for the variables from host and path.
|
||||||
|
regexp routeRegexpGroup
|
||||||
|
|
||||||
|
// List of matchers.
|
||||||
|
matchers []matcher
|
||||||
|
|
||||||
|
// The scheme used when building URLs.
|
||||||
|
buildScheme string
|
||||||
|
|
||||||
|
buildVarsFunc BuildVarsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an effective deep copy of `routeConf`
|
||||||
|
func copyRouteConf(r routeConf) routeConf {
|
||||||
|
c := r
|
||||||
|
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
c.regexp.path = copyRouteRegexp(r.regexp.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
c.regexp.host = copyRouteRegexp(r.regexp.host)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.matchers = make([]matcher, 0, len(r.matchers))
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
c.matchers = append(c.matchers, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyRouteRegexp(r *routeRegexp) *routeRegexp {
|
||||||
|
c := *r
|
||||||
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match attempts to match the given request against the router's registered routes.
|
// Match attempts to match the given request against the router's registered routes.
|
||||||
@@ -155,22 +209,18 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
handler = http.NotFoundHandler()
|
handler = http.NotFoundHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.KeepContext {
|
|
||||||
defer contextClear(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a route registered with the given name.
|
// Get returns a route registered with the given name.
|
||||||
func (r *Router) Get(name string) *Route {
|
func (r *Router) Get(name string) *Route {
|
||||||
return r.getNamedRoutes()[name]
|
return r.namedRoutes[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoute returns a route registered with the given name. This method
|
// GetRoute returns a route registered with the given name. This method
|
||||||
// was renamed to Get() and remains here for backwards compatibility.
|
// was renamed to Get() and remains here for backwards compatibility.
|
||||||
func (r *Router) GetRoute(name string) *Route {
|
func (r *Router) GetRoute(name string) *Route {
|
||||||
return r.getNamedRoutes()[name]
|
return r.namedRoutes[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||||
@@ -221,55 +271,24 @@ func (r *Router) UseEncodedPath() *Router {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func (r *Router) getBuildScheme() string {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.namedRoutes == nil {
|
|
||||||
if r.parent != nil {
|
|
||||||
r.namedRoutes = r.parent.getNamedRoutes()
|
|
||||||
} else {
|
|
||||||
r.namedRoutes = make(map[string]*Route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.namedRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
|
||||||
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getRegexpGroup()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) buildVars(m map[string]string) map[string]string {
|
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Route factories
|
// Route factories
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// NewRoute registers an empty route.
|
// NewRoute registers an empty route.
|
||||||
func (r *Router) NewRoute() *Route {
|
func (r *Router) NewRoute() *Route {
|
||||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
// initialize a route with a copy of the parent router's configuration
|
||||||
|
route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||||
r.routes = append(r.routes, route)
|
r.routes = append(r.routes, route)
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name registers a new route with a name.
|
||||||
|
// See Route.Name().
|
||||||
|
func (r *Router) Name(name string) *Route {
|
||||||
|
return r.NewRoute().Name(name)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle registers a new route with a matcher for the URL path.
|
// Handle registers a new route with a matcher for the URL path.
|
||||||
// See Route.Path() and Route.Handler().
|
// See Route.Path() and Route.Handler().
|
||||||
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||||
|
|||||||
51
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
51
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
@@ -113,6 +113,13 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
|
|||||||
if typ != regexpTypePrefix {
|
if typ != regexpTypePrefix {
|
||||||
pattern.WriteByte('$')
|
pattern.WriteByte('$')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wildcardHostPort bool
|
||||||
|
if typ == regexpTypeHost {
|
||||||
|
if !strings.Contains(pattern.String(), ":") {
|
||||||
|
wildcardHostPort = true
|
||||||
|
}
|
||||||
|
}
|
||||||
reverse.WriteString(raw)
|
reverse.WriteString(raw)
|
||||||
if endSlash {
|
if endSlash {
|
||||||
reverse.WriteByte('/')
|
reverse.WriteByte('/')
|
||||||
@@ -131,13 +138,14 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
|
|||||||
|
|
||||||
// Done!
|
// Done!
|
||||||
return &routeRegexp{
|
return &routeRegexp{
|
||||||
template: template,
|
template: template,
|
||||||
regexpType: typ,
|
regexpType: typ,
|
||||||
options: options,
|
options: options,
|
||||||
regexp: reg,
|
regexp: reg,
|
||||||
reverse: reverse.String(),
|
reverse: reverse.String(),
|
||||||
varsN: varsN,
|
varsN: varsN,
|
||||||
varsR: varsR,
|
varsR: varsR,
|
||||||
|
wildcardHostPort: wildcardHostPort,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,11 +166,22 @@ type routeRegexp struct {
|
|||||||
varsN []string
|
varsN []string
|
||||||
// Variable regexps (validators).
|
// Variable regexps (validators).
|
||||||
varsR []*regexp.Regexp
|
varsR []*regexp.Regexp
|
||||||
|
// Wildcard host-port (no strict port match in hostname)
|
||||||
|
wildcardHostPort bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match matches the regexp against the URL host or path.
|
// Match matches the regexp against the URL host or path.
|
||||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
if r.regexpType != regexpTypeHost {
|
if r.regexpType == regexpTypeHost {
|
||||||
|
host := getHost(req)
|
||||||
|
if r.wildcardHostPort {
|
||||||
|
// Don't be strict on the port match
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(host)
|
||||||
|
} else {
|
||||||
if r.regexpType == regexpTypeQuery {
|
if r.regexpType == regexpTypeQuery {
|
||||||
return r.matchQueryString(req)
|
return r.matchQueryString(req)
|
||||||
}
|
}
|
||||||
@@ -172,8 +191,6 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
|||||||
}
|
}
|
||||||
return r.regexp.MatchString(path)
|
return r.regexp.MatchString(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.regexp.MatchString(getHost(req))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// url builds a URL part using the given values.
|
// url builds a URL part using the given values.
|
||||||
@@ -267,7 +284,7 @@ type routeRegexpGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setMatch extracts the variables from the URL once a route matches.
|
// setMatch extracts the variables from the URL once a route matches.
|
||||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||||
// Store host variables.
|
// Store host variables.
|
||||||
if v.host != nil {
|
if v.host != nil {
|
||||||
host := getHost(req)
|
host := getHost(req)
|
||||||
@@ -296,7 +313,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
|||||||
} else {
|
} else {
|
||||||
u.Path += "/"
|
u.Path += "/"
|
||||||
}
|
}
|
||||||
m.Handler = http.RedirectHandler(u.String(), 301)
|
m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,17 +329,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getHost tries its best to return the request host.
|
// getHost tries its best to return the request host.
|
||||||
|
// According to section 14.23 of RFC 2616 the Host header
|
||||||
|
// can include the port number if the default value of 80 is not used.
|
||||||
func getHost(r *http.Request) string {
|
func getHost(r *http.Request) string {
|
||||||
if r.URL.IsAbs() {
|
if r.URL.IsAbs() {
|
||||||
return r.URL.Host
|
return r.URL.Host
|
||||||
}
|
}
|
||||||
host := r.Host
|
return r.Host
|
||||||
// Slice off any port information.
|
|
||||||
if i := strings.Index(host, ":"); i != -1 {
|
|
||||||
host = host[:i]
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||||
|
|||||||
141
vendor/github.com/gorilla/mux/route.go
generated
vendored
141
vendor/github.com/gorilla/mux/route.go
generated
vendored
@@ -15,24 +15,8 @@ import (
|
|||||||
|
|
||||||
// Route stores information to match a request and build URLs.
|
// Route stores information to match a request and build URLs.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
// Parent where the route was registered (a Router).
|
|
||||||
parent parentRoute
|
|
||||||
// Request handler for the route.
|
// Request handler for the route.
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
// List of matchers.
|
|
||||||
matchers []matcher
|
|
||||||
// Manager for the variables from host and path.
|
|
||||||
regexp *routeRegexpGroup
|
|
||||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
|
||||||
// redirect to the former and vice versa.
|
|
||||||
strictSlash bool
|
|
||||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
|
||||||
// will not redirect
|
|
||||||
skipClean bool
|
|
||||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
|
||||||
useEncodedPath bool
|
|
||||||
// The scheme used when building URLs.
|
|
||||||
buildScheme string
|
|
||||||
// If true, this route never matches: it is only used to build URLs.
|
// If true, this route never matches: it is only used to build URLs.
|
||||||
buildOnly bool
|
buildOnly bool
|
||||||
// The name used to build URLs.
|
// The name used to build URLs.
|
||||||
@@ -40,7 +24,11 @@ type Route struct {
|
|||||||
// Error resulted from building a route.
|
// Error resulted from building a route.
|
||||||
err error
|
err error
|
||||||
|
|
||||||
buildVarsFunc BuildVarsFunc
|
// "global" reference to all named routes
|
||||||
|
namedRoutes map[string]*Route
|
||||||
|
|
||||||
|
// config possibly passed in from `Router`
|
||||||
|
routeConf
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipClean reports whether path cleaning is enabled for this route via
|
// SkipClean reports whether path cleaning is enabled for this route via
|
||||||
@@ -64,6 +52,18 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|||||||
matchErr = ErrMethodMismatch
|
matchErr = ErrMethodMismatch
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore ErrNotFound errors. These errors arise from match call
|
||||||
|
// to Subrouters.
|
||||||
|
//
|
||||||
|
// This prevents subsequent matching subrouters from failing to
|
||||||
|
// run middleware. If not ignored, the middleware would see a
|
||||||
|
// non-nil MatchErr and be skipped, even when there was a
|
||||||
|
// matching route.
|
||||||
|
if match.MatchErr == ErrNotFound {
|
||||||
|
match.MatchErr = nil
|
||||||
|
}
|
||||||
|
|
||||||
matchErr = nil
|
matchErr = nil
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -93,9 +93,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set variables.
|
// Set variables.
|
||||||
if r.regexp != nil {
|
r.regexp.setMatch(req, match, r)
|
||||||
r.regexp.setMatch(req, match, r)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +135,7 @@ func (r *Route) GetHandler() http.Handler {
|
|||||||
// Name -----------------------------------------------------------------------
|
// Name -----------------------------------------------------------------------
|
||||||
|
|
||||||
// Name sets the name for the route, used to build URLs.
|
// Name sets the name for the route, used to build URLs.
|
||||||
// If the name was registered already it will be overwritten.
|
// It is an error to call Name more than once on a route.
|
||||||
func (r *Route) Name(name string) *Route {
|
func (r *Route) Name(name string) *Route {
|
||||||
if r.name != "" {
|
if r.name != "" {
|
||||||
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||||
@@ -145,7 +143,7 @@ func (r *Route) Name(name string) *Route {
|
|||||||
}
|
}
|
||||||
if r.err == nil {
|
if r.err == nil {
|
||||||
r.name = name
|
r.name = name
|
||||||
r.getNamedRoutes()[name] = r
|
r.namedRoutes[name] = r
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -177,7 +175,6 @@ func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
r.regexp = r.getRegexpGroup()
|
|
||||||
if typ == regexpTypePath || typ == regexpTypePrefix {
|
if typ == regexpTypePath || typ == regexpTypePrefix {
|
||||||
if len(tpl) > 0 && tpl[0] != '/' {
|
if len(tpl) > 0 && tpl[0] != '/' {
|
||||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||||
@@ -386,7 +383,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
|
|||||||
// The above route will only match if the URL contains the defined queries
|
// The above route will only match if the URL contains the defined queries
|
||||||
// values, e.g.: ?foo=bar&id=42.
|
// values, e.g.: ?foo=bar&id=42.
|
||||||
//
|
//
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
//
|
//
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
//
|
//
|
||||||
@@ -424,7 +421,7 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
|||||||
for k, v := range schemes {
|
for k, v := range schemes {
|
||||||
schemes[k] = strings.ToLower(v)
|
schemes[k] = strings.ToLower(v)
|
||||||
}
|
}
|
||||||
if r.buildScheme == "" && len(schemes) > 0 {
|
if len(schemes) > 0 {
|
||||||
r.buildScheme = schemes[0]
|
r.buildScheme = schemes[0]
|
||||||
}
|
}
|
||||||
return r.addMatcher(schemeMatcher(schemes))
|
return r.addMatcher(schemeMatcher(schemes))
|
||||||
@@ -439,7 +436,15 @@ type BuildVarsFunc func(map[string]string) map[string]string
|
|||||||
// BuildVarsFunc adds a custom function to be used to modify build variables
|
// BuildVarsFunc adds a custom function to be used to modify build variables
|
||||||
// before a route's URL is built.
|
// before a route's URL is built.
|
||||||
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
r.buildVarsFunc = f
|
if r.buildVarsFunc != nil {
|
||||||
|
// compose the old and new functions
|
||||||
|
old := r.buildVarsFunc
|
||||||
|
r.buildVarsFunc = func(m map[string]string) map[string]string {
|
||||||
|
return f(old(m))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.buildVarsFunc = f
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +463,8 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|||||||
// Here, the routes registered in the subrouter won't be tested if the host
|
// Here, the routes registered in the subrouter won't be tested if the host
|
||||||
// doesn't match.
|
// doesn't match.
|
||||||
func (r *Route) Subrouter() *Router {
|
func (r *Route) Subrouter() *Router {
|
||||||
router := &Router{parent: r, strictSlash: r.strictSlash}
|
// initialize a subrouter with a copy of the parent route's configuration
|
||||||
|
router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||||
r.addMatcher(router)
|
r.addMatcher(router)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
@@ -502,9 +508,6 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a host or path")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
values, err := r.prepareVars(pairs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -516,8 +519,8 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
scheme = "http"
|
scheme = "http"
|
||||||
if s := r.getBuildScheme(); s != "" {
|
if r.buildScheme != "" {
|
||||||
scheme = s
|
scheme = r.buildScheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.regexp.path != nil {
|
if r.regexp.path != nil {
|
||||||
@@ -547,7 +550,7 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
if r.regexp.host == nil {
|
||||||
return nil, errors.New("mux: route doesn't have a host")
|
return nil, errors.New("mux: route doesn't have a host")
|
||||||
}
|
}
|
||||||
values, err := r.prepareVars(pairs...)
|
values, err := r.prepareVars(pairs...)
|
||||||
@@ -562,8 +565,8 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
if s := r.getBuildScheme(); s != "" {
|
if r.buildScheme != "" {
|
||||||
u.Scheme = s
|
u.Scheme = r.buildScheme
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
@@ -575,7 +578,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
if r.regexp.path == nil {
|
||||||
return nil, errors.New("mux: route doesn't have a path")
|
return nil, errors.New("mux: route doesn't have a path")
|
||||||
}
|
}
|
||||||
values, err := r.prepareVars(pairs...)
|
values, err := r.prepareVars(pairs...)
|
||||||
@@ -600,7 +603,7 @@ func (r *Route) GetPathTemplate() (string, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return "", r.err
|
return "", r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
if r.regexp.path == nil {
|
||||||
return "", errors.New("mux: route doesn't have a path")
|
return "", errors.New("mux: route doesn't have a path")
|
||||||
}
|
}
|
||||||
return r.regexp.path.template, nil
|
return r.regexp.path.template, nil
|
||||||
@@ -614,7 +617,7 @@ func (r *Route) GetPathRegexp() (string, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return "", r.err
|
return "", r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
if r.regexp.path == nil {
|
||||||
return "", errors.New("mux: route does not have a path")
|
return "", errors.New("mux: route does not have a path")
|
||||||
}
|
}
|
||||||
return r.regexp.path.regexp.String(), nil
|
return r.regexp.path.regexp.String(), nil
|
||||||
@@ -629,7 +632,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
if r.regexp.queries == nil {
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
return nil, errors.New("mux: route doesn't have queries")
|
||||||
}
|
}
|
||||||
var queries []string
|
var queries []string
|
||||||
@@ -648,7 +651,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
if r.regexp.queries == nil {
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
return nil, errors.New("mux: route doesn't have queries")
|
||||||
}
|
}
|
||||||
var queries []string
|
var queries []string
|
||||||
@@ -683,7 +686,7 @@ func (r *Route) GetHostTemplate() (string, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return "", r.err
|
return "", r.err
|
||||||
}
|
}
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
if r.regexp.host == nil {
|
||||||
return "", errors.New("mux: route doesn't have a host")
|
return "", errors.New("mux: route doesn't have a host")
|
||||||
}
|
}
|
||||||
return r.regexp.host.template, nil
|
return r.regexp.host.template, nil
|
||||||
@@ -700,64 +703,8 @@ func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) buildVars(m map[string]string) map[string]string {
|
func (r *Route) buildVars(m map[string]string) map[string]string {
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
if r.buildVarsFunc != nil {
|
if r.buildVarsFunc != nil {
|
||||||
m = r.buildVarsFunc(m)
|
m = r.buildVarsFunc(m)
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// parentRoute allows routes to know about parent host and path definitions.
|
|
||||||
type parentRoute interface {
|
|
||||||
getBuildScheme() string
|
|
||||||
getNamedRoutes() map[string]*Route
|
|
||||||
getRegexpGroup() *routeRegexpGroup
|
|
||||||
buildVars(map[string]string) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) getBuildScheme() string {
|
|
||||||
if r.buildScheme != "" {
|
|
||||||
return r.buildScheme
|
|
||||||
}
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
return r.parent.getNamedRoutes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from this route.
|
|
||||||
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.regexp == nil {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
regexp := r.parent.getRegexpGroup()
|
|
||||||
if regexp == nil {
|
|
||||||
r.regexp = new(routeRegexpGroup)
|
|
||||||
} else {
|
|
||||||
// Copy.
|
|
||||||
r.regexp = &routeRegexpGroup{
|
|
||||||
host: regexp.host,
|
|
||||||
path: regexp.path,
|
|
||||||
queries: regexp.queries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.regexp
|
|
||||||
}
|
|
||||||
|
|||||||
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
@@ -22,4 +22,4 @@ _testmain.go
|
|||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
@@ -3,11 +3,11 @@ sudo: false
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- go: 1.4
|
- go: 1.7.x
|
||||||
- go: 1.5
|
- go: 1.8.x
|
||||||
- go: 1.6
|
- go: 1.9.x
|
||||||
- go: 1.7
|
- go: 1.10.x
|
||||||
- go: 1.8
|
- go: 1.11.x
|
||||||
- go: tip
|
- go: tip
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- go: tip
|
- go: tip
|
||||||
|
|||||||
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
@@ -4,5 +4,6 @@
|
|||||||
# Please keep the list sorted.
|
# Please keep the list sorted.
|
||||||
|
|
||||||
Gary Burd <gary@beagledreams.com>
|
Gary Burd <gary@beagledreams.com>
|
||||||
|
Google LLC (https://opensource.google.com/)
|
||||||
Joachim Bauch <mail@joachim-bauch.de>
|
Joachim Bauch <mail@joachim-bauch.de>
|
||||||
|
|
||||||
|
|||||||
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
@@ -51,7 +51,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
|||||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
||||||
2. The application can get the type of a received data message by implementing
|
2. The application can get the type of a received data message by implementing
|
||||||
|
|||||||
253
vendor/github.com/gorilla/websocket/client.go
generated
vendored
253
vendor/github.com/gorilla/websocket/client.go
generated
vendored
@@ -5,15 +5,15 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -53,6 +53,10 @@ type Dialer struct {
|
|||||||
// NetDial is nil, net.Dial is used.
|
// NetDial is nil, net.Dial is used.
|
||||||
NetDial func(network, addr string) (net.Conn, error)
|
NetDial func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||||
|
// NetDialContext is nil, net.DialContext is used.
|
||||||
|
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
// Proxy specifies a function to return a proxy for a given
|
// Proxy specifies a function to return a proxy for a given
|
||||||
// Request. If the function returns a non-nil error, the
|
// Request. If the function returns a non-nil error, the
|
||||||
// request is aborted with the provided error.
|
// request is aborted with the provided error.
|
||||||
@@ -71,6 +75,17 @@ type Dialer struct {
|
|||||||
// do not limit the size of the messages that can be sent or received.
|
// do not limit the size of the messages that can be sent or received.
|
||||||
ReadBufferSize, WriteBufferSize int
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
// Subprotocols specifies the client's requested subprotocols.
|
// Subprotocols specifies the client's requested subprotocols.
|
||||||
Subprotocols []string
|
Subprotocols []string
|
||||||
|
|
||||||
@@ -86,52 +101,13 @@ type Dialer struct {
|
|||||||
Jar http.CookieJar
|
Jar http.CookieJar
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
// Dial creates a new client connection by calling DialContext with a background context.
|
||||||
|
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
// parseURL parses the URL.
|
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||||
//
|
|
||||||
// This function is a replacement for the standard library url.Parse function.
|
|
||||||
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
|
||||||
func parseURL(s string) (*url.URL, error) {
|
|
||||||
// From the RFC:
|
|
||||||
//
|
|
||||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
|
||||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
|
||||||
var u url.URL
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(s, "ws://"):
|
|
||||||
u.Scheme = "ws"
|
|
||||||
s = s[len("ws://"):]
|
|
||||||
case strings.HasPrefix(s, "wss://"):
|
|
||||||
u.Scheme = "wss"
|
|
||||||
s = s[len("wss://"):]
|
|
||||||
default:
|
|
||||||
return nil, errMalformedURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := strings.Index(s, "?"); i >= 0 {
|
|
||||||
u.RawQuery = s[i+1:]
|
|
||||||
s = s[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := strings.Index(s, "/"); i >= 0 {
|
|
||||||
u.Opaque = s[i:]
|
|
||||||
s = s[:i]
|
|
||||||
} else {
|
|
||||||
u.Opaque = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
u.Host = s
|
|
||||||
|
|
||||||
if strings.Contains(u.Host, "@") {
|
|
||||||
// Don't bother parsing user information because user information is
|
|
||||||
// not allowed in websocket URIs.
|
|
||||||
return nil, errMalformedURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return &u, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
|
|
||||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||||
hostPort = u.Host
|
hostPort = u.Host
|
||||||
hostNoPort = u.Host
|
hostNoPort = u.Host
|
||||||
@@ -150,26 +126,29 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
|||||||
return hostPort, hostNoPort
|
return hostPort, hostNoPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
// DefaultDialer is a dialer with all fields set to the default values.
|
||||||
var DefaultDialer = &Dialer{
|
var DefaultDialer = &Dialer{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
HandshakeTimeout: 45 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial creates a new client connection. Use requestHeader to specify the
|
// nilDialer is dialer to use when receiver is nil.
|
||||||
|
var nilDialer = *DefaultDialer
|
||||||
|
|
||||||
|
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||||
// Use the response.Header to get the selected subprotocol
|
// Use the response.Header to get the selected subprotocol
|
||||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
//
|
//
|
||||||
|
// The context will be used in the request and in the Dialer
|
||||||
|
//
|
||||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
// etcetera. The response body may not contain the entire response and does not
|
// etcetera. The response body may not contain the entire response and does not
|
||||||
// need to be closed by the application.
|
// need to be closed by the application.
|
||||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
|
||||||
if d == nil {
|
if d == nil {
|
||||||
d = &Dialer{
|
d = &nilDialer
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeKey, err := generateChallengeKey()
|
challengeKey, err := generateChallengeKey()
|
||||||
@@ -177,7 +156,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := parseURL(urlStr)
|
u, err := url.Parse(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -205,6 +184,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
Host: u.Host,
|
Host: u.Host,
|
||||||
}
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
// Set the cookies present in the cookie jar of the dialer
|
// Set the cookies present in the cookie jar of the dialer
|
||||||
if d.Jar != nil {
|
if d.Jar != nil {
|
||||||
@@ -237,45 +217,83 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
k == "Sec-Websocket-Extensions" ||
|
k == "Sec-Websocket-Extensions" ||
|
||||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
|
case k == "Sec-Websocket-Protocol":
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||||
default:
|
default:
|
||||||
req.Header[k] = vs
|
req.Header[k] = vs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.EnableCompression {
|
if d.EnableCompression {
|
||||||
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HandshakeTimeout != 0 {
|
||||||
|
var cancel func()
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get network dial function.
|
||||||
|
var netDial func(network, add string) (net.Conn, error)
|
||||||
|
|
||||||
|
if d.NetDialContext != nil {
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return d.NetDialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
} else if d.NetDial != nil {
|
||||||
|
netDial = d.NetDial
|
||||||
|
} else {
|
||||||
|
netDialer := &net.Dialer{}
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return netDialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to set the connection deadline.
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
forwardDial := netDial
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := forwardDial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = c.SetDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to connect through a proxy.
|
||||||
|
if d.Proxy != nil {
|
||||||
|
proxyURL, err := d.Proxy(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if proxyURL != nil {
|
||||||
|
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
netDial = dialer.Dial
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort, hostNoPort := hostPortNoPort(u)
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
trace := httptrace.ContextClientTrace(ctx)
|
||||||
var proxyURL *url.URL
|
if trace != nil && trace.GetConn != nil {
|
||||||
// Check wether the proxy method has been configured
|
trace.GetConn(hostPort)
|
||||||
if d.Proxy != nil {
|
|
||||||
proxyURL, err = d.Proxy(req)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetHostPort string
|
netConn, err := netDial("tcp", hostPort)
|
||||||
if proxyURL != nil {
|
if trace != nil && trace.GotConn != nil {
|
||||||
targetHostPort, _ = hostPortNoPort(proxyURL)
|
trace.GotConn(httptrace.GotConnInfo{
|
||||||
} else {
|
Conn: netConn,
|
||||||
targetHostPort = hostPort
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadline time.Time
|
|
||||||
if d.HandshakeTimeout != 0 {
|
|
||||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
netDial := d.NetDial
|
|
||||||
if netDial == nil {
|
|
||||||
netDialer := &net.Dialer{Deadline: deadline}
|
|
||||||
netDial = netDialer.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
netConn, err := netDial("tcp", targetHostPort)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -286,42 +304,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := netConn.SetDeadline(deadline); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxyURL != nil {
|
|
||||||
connectHeader := make(http.Header)
|
|
||||||
if user := proxyURL.User; user != nil {
|
|
||||||
proxyUser := user.Username()
|
|
||||||
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
|
||||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
|
||||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectReq := &http.Request{
|
|
||||||
Method: "CONNECT",
|
|
||||||
URL: &url.URL{Opaque: hostPort},
|
|
||||||
Host: hostPort,
|
|
||||||
Header: connectHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
connectReq.Write(netConn)
|
|
||||||
|
|
||||||
// Read response.
|
|
||||||
// Okay to use and discard buffered reader here, because
|
|
||||||
// TLS server will not speak until spoken to.
|
|
||||||
br := bufio.NewReader(netConn)
|
|
||||||
resp, err := http.ReadResponse(br, connectReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
f := strings.SplitN(resp.Status, " ", 2)
|
|
||||||
return nil, nil, errors.New(f[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "https" {
|
if u.Scheme == "https" {
|
||||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||||
if cfg.ServerName == "" {
|
if cfg.ServerName == "" {
|
||||||
@@ -329,22 +311,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
}
|
}
|
||||||
tlsConn := tls.Client(netConn, cfg)
|
tlsConn := tls.Client(netConn, cfg)
|
||||||
netConn = tlsConn
|
netConn = tlsConn
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
|
||||||
return nil, nil, err
|
var err error
|
||||||
|
if trace != nil {
|
||||||
|
err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
||||||
|
} else {
|
||||||
|
err = doHandshake(tlsConn, cfg)
|
||||||
}
|
}
|
||||||
if !cfg.InsecureSkipVerify {
|
|
||||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||||
|
|
||||||
if err := req.Write(netConn); err != nil {
|
if err := req.Write(netConn); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||||
|
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||||
|
trace.GotFirstResponseByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.ReadResponse(conn.br, req)
|
resp, err := http.ReadResponse(conn.br, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -390,3 +381,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
netConn = nil // to avoid close in defer.
|
netConn = nil // to avoid close in defer.
|
||||||
return conn, resp, nil
|
return conn, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !cfg.InsecureSkipVerify {
|
||||||
|
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
174
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
174
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@@ -76,7 +76,7 @@ const (
|
|||||||
// is UTF-8 encoded text.
|
// is UTF-8 encoded text.
|
||||||
PingMessage = 9
|
PingMessage = 9
|
||||||
|
|
||||||
// PongMessage denotes a ping control message. The optional message payload
|
// PongMessage denotes a pong control message. The optional message payload
|
||||||
// is UTF-8 encoded text.
|
// is UTF-8 encoded text.
|
||||||
PongMessage = 10
|
PongMessage = 10
|
||||||
)
|
)
|
||||||
@@ -100,9 +100,8 @@ func (e *netError) Error() string { return e.msg }
|
|||||||
func (e *netError) Temporary() bool { return e.temporary }
|
func (e *netError) Temporary() bool { return e.temporary }
|
||||||
func (e *netError) Timeout() bool { return e.timeout }
|
func (e *netError) Timeout() bool { return e.timeout }
|
||||||
|
|
||||||
// CloseError represents close frame.
|
// CloseError represents a close message.
|
||||||
type CloseError struct {
|
type CloseError struct {
|
||||||
|
|
||||||
// Code is defined in RFC 6455, section 11.7.
|
// Code is defined in RFC 6455, section 11.7.
|
||||||
Code int
|
Code int
|
||||||
|
|
||||||
@@ -224,6 +223,20 @@ func isValidReceivedCloseCode(code int) bool {
|
|||||||
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this
|
||||||
|
// interface. The type of the value stored in a pool is not specified.
|
||||||
|
type BufferPool interface {
|
||||||
|
// Get gets a value from the pool or returns nil if the pool is empty.
|
||||||
|
Get() interface{}
|
||||||
|
// Put adds a value to the pool.
|
||||||
|
Put(interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePoolData is the type added to the write buffer pool. This wrapper is
|
||||||
|
// used to prevent applications from peeking at and depending on the values
|
||||||
|
// added to the pool.
|
||||||
|
type writePoolData struct{ buf []byte }
|
||||||
|
|
||||||
// The Conn type represents a WebSocket connection.
|
// The Conn type represents a WebSocket connection.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
@@ -233,6 +246,8 @@ type Conn struct {
|
|||||||
// Write fields
|
// Write fields
|
||||||
mu chan bool // used as mutex to protect write to conn
|
mu chan bool // used as mutex to protect write to conn
|
||||||
writeBuf []byte // frame is constructed in this buffer.
|
writeBuf []byte // frame is constructed in this buffer.
|
||||||
|
writePool BufferPool
|
||||||
|
writeBufSize int
|
||||||
writeDeadline time.Time
|
writeDeadline time.Time
|
||||||
writer io.WriteCloser // the current writer returned to the application
|
writer io.WriteCloser // the current writer returned to the application
|
||||||
isWriting bool // for best-effort concurrent write detection
|
isWriting bool // for best-effort concurrent write detection
|
||||||
@@ -264,64 +279,29 @@ type Conn struct {
|
|||||||
newDecompressionReader func(io.Reader) io.ReadCloser
|
newDecompressionReader func(io.Reader) io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn {
|
||||||
return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeHook struct {
|
|
||||||
p []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wh *writeHook) Write(p []byte) (int, error) {
|
|
||||||
wh.p = p
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
|
|
||||||
mu := make(chan bool, 1)
|
|
||||||
mu <- true
|
|
||||||
|
|
||||||
var br *bufio.Reader
|
|
||||||
if readBufferSize == 0 && brw != nil && brw.Reader != nil {
|
|
||||||
// Reuse the supplied bufio.Reader if the buffer has a useful size.
|
|
||||||
// This code assumes that peek on a reader returns
|
|
||||||
// bufio.Reader.buf[:0].
|
|
||||||
brw.Reader.Reset(conn)
|
|
||||||
if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
|
|
||||||
br = brw.Reader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if br == nil {
|
if br == nil {
|
||||||
if readBufferSize == 0 {
|
if readBufferSize == 0 {
|
||||||
readBufferSize = defaultReadBufferSize
|
readBufferSize = defaultReadBufferSize
|
||||||
}
|
} else if readBufferSize < maxControlFramePayloadSize {
|
||||||
if readBufferSize < maxControlFramePayloadSize {
|
// must be large enough for control frame
|
||||||
readBufferSize = maxControlFramePayloadSize
|
readBufferSize = maxControlFramePayloadSize
|
||||||
}
|
}
|
||||||
br = bufio.NewReaderSize(conn, readBufferSize)
|
br = bufio.NewReaderSize(conn, readBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var writeBuf []byte
|
if writeBufferSize <= 0 {
|
||||||
if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
|
writeBufferSize = defaultWriteBufferSize
|
||||||
// Use the bufio.Writer's buffer if the buffer has a useful size. This
|
}
|
||||||
// code assumes that bufio.Writer.buf[:1] is passed to the
|
writeBufferSize += maxFrameHeaderSize
|
||||||
// bufio.Writer's underlying writer.
|
|
||||||
var wh writeHook
|
if writeBuf == nil && writeBufferPool == nil {
|
||||||
brw.Writer.Reset(&wh)
|
writeBuf = make([]byte, writeBufferSize)
|
||||||
brw.Writer.WriteByte(0)
|
|
||||||
brw.Flush()
|
|
||||||
if cap(wh.p) >= maxFrameHeaderSize+256 {
|
|
||||||
writeBuf = wh.p[:cap(wh.p)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if writeBuf == nil {
|
|
||||||
if writeBufferSize == 0 {
|
|
||||||
writeBufferSize = defaultWriteBufferSize
|
|
||||||
}
|
|
||||||
writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mu := make(chan bool, 1)
|
||||||
|
mu <- true
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
isServer: isServer,
|
isServer: isServer,
|
||||||
br: br,
|
br: br,
|
||||||
@@ -329,6 +309,8 @@ func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize in
|
|||||||
mu: mu,
|
mu: mu,
|
||||||
readFinal: true,
|
readFinal: true,
|
||||||
writeBuf: writeBuf,
|
writeBuf: writeBuf,
|
||||||
|
writePool: writeBufferPool,
|
||||||
|
writeBufSize: writeBufferSize,
|
||||||
enableWriteCompression: true,
|
enableWriteCompression: true,
|
||||||
compressionLevel: defaultCompressionLevel,
|
compressionLevel: defaultCompressionLevel,
|
||||||
}
|
}
|
||||||
@@ -343,7 +325,8 @@ func (c *Conn) Subprotocol() string {
|
|||||||
return c.subprotocol
|
return c.subprotocol
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the underlying network connection without sending or waiting for a close frame.
|
// Close closes the underlying network connection without sending or waiting
|
||||||
|
// for a close message.
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
@@ -370,7 +353,16 @@ func (c *Conn) writeFatal(err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
c.br.Discard(len(p))
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||||
<-c.mu
|
<-c.mu
|
||||||
defer func() { c.mu <- true }()
|
defer func() { c.mu <- true }()
|
||||||
|
|
||||||
@@ -382,15 +374,14 @@ func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.conn.SetWriteDeadline(deadline)
|
c.conn.SetWriteDeadline(deadline)
|
||||||
for _, buf := range bufs {
|
if len(buf1) == 0 {
|
||||||
if len(buf) > 0 {
|
_, err = c.conn.Write(buf0)
|
||||||
_, err := c.conn.Write(buf)
|
} else {
|
||||||
if err != nil {
|
err = c.writeBufs(buf0, buf1)
|
||||||
return c.writeFatal(err)
|
}
|
||||||
}
|
if err != nil {
|
||||||
}
|
return c.writeFatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if frameType == CloseMessage {
|
if frameType == CloseMessage {
|
||||||
c.writeFatal(ErrCloseSent)
|
c.writeFatal(ErrCloseSent)
|
||||||
}
|
}
|
||||||
@@ -476,7 +467,19 @@ func (c *Conn) prepWrite(messageType int) error {
|
|||||||
c.writeErrMu.Lock()
|
c.writeErrMu.Lock()
|
||||||
err := c.writeErr
|
err := c.writeErr
|
||||||
c.writeErrMu.Unlock()
|
c.writeErrMu.Unlock()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.writeBuf == nil {
|
||||||
|
wpd, ok := c.writePool.Get().(writePoolData)
|
||||||
|
if ok {
|
||||||
|
c.writeBuf = wpd.buf
|
||||||
|
} else {
|
||||||
|
c.writeBuf = make([]byte, c.writeBufSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextWriter returns a writer for the next message to send. The writer's Close
|
// NextWriter returns a writer for the next message to send. The writer's Close
|
||||||
@@ -484,6 +487,9 @@ func (c *Conn) prepWrite(messageType int) error {
|
|||||||
//
|
//
|
||||||
// There can be at most one open writer on a connection. NextWriter closes the
|
// There can be at most one open writer on a connection. NextWriter closes the
|
||||||
// previous writer if the application has not already done so.
|
// previous writer if the application has not already done so.
|
||||||
|
//
|
||||||
|
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||||
|
// PongMessage) are supported.
|
||||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||||
if err := c.prepWrite(messageType); err != nil {
|
if err := c.prepWrite(messageType); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -599,6 +605,10 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
|||||||
|
|
||||||
if final {
|
if final {
|
||||||
c.writer = nil
|
c.writer = nil
|
||||||
|
if c.writePool != nil {
|
||||||
|
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||||
|
c.writeBuf = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,7 +774,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
|
|||||||
// Read methods
|
// Read methods
|
||||||
|
|
||||||
func (c *Conn) advanceFrame() (int, error) {
|
func (c *Conn) advanceFrame() (int, error) {
|
||||||
|
|
||||||
// 1. Skip remainder of previous frame.
|
// 1. Skip remainder of previous frame.
|
||||||
|
|
||||||
if c.readRemaining > 0 {
|
if c.readRemaining > 0 {
|
||||||
@@ -1033,7 +1042,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||||
// message exceeds the limit, the connection sends a close frame to the peer
|
// message exceeds the limit, the connection sends a close message to the peer
|
||||||
// and returns ErrReadLimit to the application.
|
// and returns ErrReadLimit to the application.
|
||||||
func (c *Conn) SetReadLimit(limit int64) {
|
func (c *Conn) SetReadLimit(limit int64) {
|
||||||
c.readLimit = limit
|
c.readLimit = limit
|
||||||
@@ -1046,24 +1055,22 @@ func (c *Conn) CloseHandler() func(code int, text string) error {
|
|||||||
|
|
||||||
// SetCloseHandler sets the handler for close messages received from the peer.
|
// SetCloseHandler sets the handler for close messages received from the peer.
|
||||||
// The code argument to h is the received close code or CloseNoStatusReceived
|
// The code argument to h is the received close code or CloseNoStatusReceived
|
||||||
// if the close message is empty. The default close handler sends a close frame
|
// if the close message is empty. The default close handler sends a close
|
||||||
// back to the peer.
|
// message back to the peer.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process close messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// close messages as described in the section on Control Messages above.
|
||||||
//
|
//
|
||||||
// The connection read methods return a CloseError when a close frame is
|
// The connection read methods return a CloseError when a close message is
|
||||||
// received. Most applications should handle close messages as part of their
|
// received. Most applications should handle close messages as part of their
|
||||||
// normal error handling. Applications should only set a close handler when the
|
// normal error handling. Applications should only set a close handler when the
|
||||||
// application must perform some action before sending a close frame back to
|
// application must perform some action before sending a close message back to
|
||||||
// the peer.
|
// the peer.
|
||||||
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(code int, text string) error {
|
h = func(code int, text string) error {
|
||||||
message := []byte{}
|
message := FormatCloseMessage(code, "")
|
||||||
if code != CloseNoStatusReceived {
|
|
||||||
message = FormatCloseMessage(code, "")
|
|
||||||
}
|
|
||||||
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1077,11 +1084,12 @@ func (c *Conn) PingHandler() func(appData string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||||
// The appData argument to h is the PING frame application data. The default
|
// The appData argument to h is the PING message application data. The default
|
||||||
// ping handler sends a pong to the peer.
|
// ping handler sends a pong to the peer.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// ping messages as described in the section on Control Messages above.
|
||||||
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(message string) error {
|
h = func(message string) error {
|
||||||
@@ -1103,11 +1111,12 @@ func (c *Conn) PongHandler() func(appData string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||||
// The appData argument to h is the PONG frame application data. The default
|
// The appData argument to h is the PONG message application data. The default
|
||||||
// pong handler does nothing.
|
// pong handler does nothing.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// pong messages as described in the section on Control Messages above.
|
||||||
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(string) error { return nil }
|
h = func(string) error { return nil }
|
||||||
@@ -1141,7 +1150,14 @@ func (c *Conn) SetCompressionLevel(level int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
||||||
|
// An empty message is returned for code CloseNoStatusReceived.
|
||||||
func FormatCloseMessage(closeCode int, text string) []byte {
|
func FormatCloseMessage(closeCode int, text string) []byte {
|
||||||
|
if closeCode == CloseNoStatusReceived {
|
||||||
|
// Return empty message because it's illegal to send
|
||||||
|
// CloseNoStatusReceived. Return non-nil value in case application
|
||||||
|
// checks for nil.
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
buf := make([]byte, 2+len(text))
|
buf := make([]byte, 2+len(text))
|
||||||
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
||||||
copy(buf[2:], text)
|
copy(buf[2:], text)
|
||||||
|
|||||||
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.5
|
|
||||||
|
|
||||||
package websocket
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
func (c *Conn) read(n int) ([]byte, error) {
|
|
||||||
p, err := c.br.Peek(n)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = errUnexpectedEOF
|
|
||||||
}
|
|
||||||
if len(p) > 0 {
|
|
||||||
// advance over the bytes just read
|
|
||||||
io.ReadFull(c.br, p)
|
|
||||||
}
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.5
|
// +build go1.8
|
||||||
|
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import "io"
|
import "net"
|
||||||
|
|
||||||
func (c *Conn) read(n int) ([]byte, error) {
|
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||||
p, err := c.br.Peek(n)
|
b := net.Buffers(bufs)
|
||||||
if err == io.EOF {
|
_, err := b.WriteTo(c.conn)
|
||||||
err = errUnexpectedEOF
|
return err
|
||||||
}
|
|
||||||
c.br.Discard(len(p))
|
|
||||||
return p, err
|
|
||||||
}
|
}
|
||||||
18
vendor/github.com/gorilla/websocket/conn_write_legacy.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_write_legacy.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||||
|
for _, buf := range bufs {
|
||||||
|
if len(buf) > 0 {
|
||||||
|
if _, err := c.conn.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
56
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
56
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
@@ -6,9 +6,8 @@
|
|||||||
//
|
//
|
||||||
// Overview
|
// Overview
|
||||||
//
|
//
|
||||||
// The Conn type represents a WebSocket connection. A server application uses
|
// The Conn type represents a WebSocket connection. A server application calls
|
||||||
// the Upgrade function from an Upgrader object with a HTTP request handler
|
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||||
// to get a pointer to a Conn:
|
|
||||||
//
|
//
|
||||||
// var upgrader = websocket.Upgrader{
|
// var upgrader = websocket.Upgrader{
|
||||||
// ReadBufferSize: 1024,
|
// ReadBufferSize: 1024,
|
||||||
@@ -31,10 +30,12 @@
|
|||||||
// for {
|
// for {
|
||||||
// messageType, p, err := conn.ReadMessage()
|
// messageType, p, err := conn.ReadMessage()
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
// if err = conn.WriteMessage(messageType, p); err != nil {
|
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||||
// return err
|
// log.Println(err)
|
||||||
|
// return
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -85,20 +86,26 @@
|
|||||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||||
// methods to send a control message to the peer.
|
// methods to send a control message to the peer.
|
||||||
//
|
//
|
||||||
// Connections handle received close messages by sending a close message to the
|
// Connections handle received close messages by calling the handler function
|
||||||
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||||
// message Read method.
|
// NextReader, ReadMessage or the message Read method. The default close
|
||||||
|
// handler sends a close message to the peer.
|
||||||
//
|
//
|
||||||
// Connections handle received ping and pong messages by invoking callback
|
// Connections handle received ping messages by calling the handler function
|
||||||
// functions set with SetPingHandler and SetPongHandler methods. The callback
|
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||||
// functions are called from the NextReader, ReadMessage and the message Read
|
// message to the peer.
|
||||||
// methods.
|
|
||||||
//
|
//
|
||||||
// The default ping handler sends a pong to the peer. The application's reading
|
// Connections handle received pong messages by calling the handler function
|
||||||
// goroutine can block for a short time while the handler writes the pong data
|
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||||
// to the connection.
|
// If an application sends ping messages, then the application should set a
|
||||||
|
// pong handler to receive the corresponding pong.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping, pong and close
|
// The control message handler functions are called from the NextReader,
|
||||||
|
// ReadMessage and message reader Read methods. The default close and ping
|
||||||
|
// handlers can block these methods for a short time when the handler writes to
|
||||||
|
// the connection.
|
||||||
|
//
|
||||||
|
// The application must read the connection to process close, ping and pong
|
||||||
// messages sent from the peer. If the application is not otherwise interested
|
// messages sent from the peer. If the application is not otherwise interested
|
||||||
// in messages from the peer, then the application should start a goroutine to
|
// in messages from the peer, then the application should start a goroutine to
|
||||||
// read and discard messages from the peer. A simple example is:
|
// read and discard messages from the peer. A simple example is:
|
||||||
@@ -137,19 +144,12 @@
|
|||||||
// method fails the WebSocket handshake with HTTP status 403.
|
// method fails the WebSocket handshake with HTTP status 403.
|
||||||
//
|
//
|
||||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||||
// the handshake if the Origin request header is present and not equal to the
|
// the handshake if the Origin request header is present and the Origin host is
|
||||||
// Host request header.
|
// not equal to the Host request header.
|
||||||
//
|
//
|
||||||
// An application can allow connections from any origin by specifying a
|
// The deprecated package-level Upgrade function does not perform origin
|
||||||
// function that always returns true:
|
// checking. The application is responsible for checking the Origin header
|
||||||
//
|
// before calling the Upgrade function.
|
||||||
// var upgrader = websocket.Upgrader{
|
|
||||||
// CheckOrigin: func(r *http.Request) bool { return true },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
|
||||||
// application's responsibility to check the Origin header before calling
|
|
||||||
// Upgrade.
|
|
||||||
//
|
//
|
||||||
// Compression EXPERIMENTAL
|
// Compression EXPERIMENTAL
|
||||||
//
|
//
|
||||||
|
|||||||
11
vendor/github.com/gorilla/websocket/json.go
generated
vendored
11
vendor/github.com/gorilla/websocket/json.go
generated
vendored
@@ -9,12 +9,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteJSON is deprecated, use c.WriteJSON instead.
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.WriteJSON instead.
|
||||||
func WriteJSON(c *Conn, v interface{}) error {
|
func WriteJSON(c *Conn, v interface{}) error {
|
||||||
return c.WriteJSON(v)
|
return c.WriteJSON(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteJSON writes the JSON encoding of v to the connection.
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
//
|
//
|
||||||
// See the documentation for encoding/json Marshal for details about the
|
// See the documentation for encoding/json Marshal for details about the
|
||||||
// conversion of Go values to JSON.
|
// conversion of Go values to JSON.
|
||||||
@@ -31,7 +33,10 @@ func (c *Conn) WriteJSON(v interface{}) error {
|
|||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadJSON is deprecated, use c.ReadJSON instead.
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.ReadJSON instead.
|
||||||
func ReadJSON(c *Conn, v interface{}) error {
|
func ReadJSON(c *Conn, v interface{}) error {
|
||||||
return c.ReadJSON(v)
|
return c.ReadJSON(v)
|
||||||
}
|
}
|
||||||
|
|||||||
1
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
1
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
@@ -11,7 +11,6 @@ import "unsafe"
|
|||||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
|
||||||
// Mask one byte at a time for small buffers.
|
// Mask one byte at a time for small buffers.
|
||||||
if len(b) < 2*wordSize {
|
if len(b) < 2*wordSize {
|
||||||
for i := range b {
|
for i := range b {
|
||||||
|
|||||||
1
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
1
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
@@ -19,7 +19,6 @@ import (
|
|||||||
type PreparedMessage struct {
|
type PreparedMessage struct {
|
||||||
messageType int
|
messageType int
|
||||||
data []byte
|
data []byte
|
||||||
err error
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
frames map[prepareKey]*preparedFrame
|
frames map[prepareKey]*preparedFrame
|
||||||
}
|
}
|
||||||
|
|||||||
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netDialerFunc func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return fn(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProxyDialer struct {
|
||||||
|
proxyURL *url.URL
|
||||||
|
fowardDial func(network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||||
|
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||||
|
conn, err := hpd.fowardDial(network, hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectHeader := make(http.Header)
|
||||||
|
if user := hpd.proxyURL.User; user != nil {
|
||||||
|
proxyUser := user.Username()
|
||||||
|
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||||
|
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||||
|
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{Opaque: addr},
|
||||||
|
Host: addr,
|
||||||
|
Header: connectHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := connectReq.Write(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response. It's OK to use and discard buffered reader here becaue
|
||||||
|
// the remote server does not speak until spoken to.
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
resp, err := http.ReadResponse(br, connectReq)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
conn.Close()
|
||||||
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
|
return nil, errors.New(f[1])
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
136
vendor/github.com/gorilla/websocket/server.go
generated
vendored
136
vendor/github.com/gorilla/websocket/server.go
generated
vendored
@@ -7,7 +7,7 @@ package websocket
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,10 +33,23 @@ type Upgrader struct {
|
|||||||
// or received.
|
// or received.
|
||||||
ReadBufferSize, WriteBufferSize int
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
// Subprotocols specifies the server's supported protocols in order of
|
// Subprotocols specifies the server's supported protocols in order of
|
||||||
// preference. If this field is set, then the Upgrade method negotiates a
|
// preference. If this field is not nil, then the Upgrade method negotiates a
|
||||||
// subprotocol by selecting the first match in this list with a protocol
|
// subprotocol by selecting the first match in this list with a protocol
|
||||||
// requested by the client.
|
// requested by the client. If there's no match, then no protocol is
|
||||||
|
// negotiated (the Sec-Websocket-Protocol header is not included in the
|
||||||
|
// handshake response).
|
||||||
Subprotocols []string
|
Subprotocols []string
|
||||||
|
|
||||||
// Error specifies the function for generating HTTP error responses. If Error
|
// Error specifies the function for generating HTTP error responses. If Error
|
||||||
@@ -44,8 +57,12 @@ type Upgrader struct {
|
|||||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||||
|
|
||||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
// CheckOrigin is nil, then a safe default is used: return false if the
|
||||||
// must match the host of the request.
|
// Origin request header is present and the origin host is not equal to
|
||||||
|
// request Host header.
|
||||||
|
//
|
||||||
|
// A CheckOrigin function should carefully validate the request origin to
|
||||||
|
// prevent cross-site request forgery.
|
||||||
CheckOrigin func(r *http.Request) bool
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
|
||||||
// EnableCompression specify if the server should attempt to negotiate per
|
// EnableCompression specify if the server should attempt to negotiate per
|
||||||
@@ -76,7 +93,7 @@ func checkSameOrigin(r *http.Request) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.Host == r.Host
|
return equalASCIIFold(u.Host, r.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||||
@@ -99,42 +116,44 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
|
|||||||
//
|
//
|
||||||
// The responseHeader is included in the response to the client's upgrade
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
// application negotiated subprotocol (Sec-WebSocket-Protocol).
|
||||||
//
|
//
|
||||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||||
// response.
|
// response.
|
||||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||||
if r.Method != "GET" {
|
const badHandshake = "websocket: the client is not using the websocket protocol: "
|
||||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
checkOrigin := u.CheckOrigin
|
checkOrigin := u.CheckOrigin
|
||||||
if checkOrigin == nil {
|
if checkOrigin == nil {
|
||||||
checkOrigin = checkSameOrigin
|
checkOrigin = checkSameOrigin
|
||||||
}
|
}
|
||||||
if !checkOrigin(r) {
|
if !checkOrigin(r) {
|
||||||
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||||
if challengeKey == "" {
|
if challengeKey == "" {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||||
@@ -151,17 +170,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
netConn net.Conn
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
h, ok := w.(http.Hijacker)
|
h, ok := w.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||||
}
|
}
|
||||||
var brw *bufio.ReadWriter
|
var brw *bufio.ReadWriter
|
||||||
netConn, brw, err = h.Hijack()
|
netConn, brw, err := h.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
@@ -171,7 +185,21 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
var br *bufio.Reader
|
||||||
|
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {
|
||||||
|
// Reuse hijacked buffered reader as connection reader.
|
||||||
|
br = brw.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufioWriterBuffer(netConn, brw.Writer)
|
||||||
|
|
||||||
|
var writeBuf []byte
|
||||||
|
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
|
||||||
|
// Reuse hijacked write buffer as connection buffer.
|
||||||
|
writeBuf = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)
|
||||||
c.subprotocol = subprotocol
|
c.subprotocol = subprotocol
|
||||||
|
|
||||||
if compress {
|
if compress {
|
||||||
@@ -179,17 +207,23 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
c.newDecompressionReader = decompressNoContextTakeover
|
c.newDecompressionReader = decompressNoContextTakeover
|
||||||
}
|
}
|
||||||
|
|
||||||
p := c.writeBuf[:0]
|
// Use larger of hijacked buffer and connection write buffer for header.
|
||||||
|
p := buf
|
||||||
|
if len(c.writeBuf) > len(p) {
|
||||||
|
p = c.writeBuf
|
||||||
|
}
|
||||||
|
p = p[:0]
|
||||||
|
|
||||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||||
p = append(p, computeAcceptKey(challengeKey)...)
|
p = append(p, computeAcceptKey(challengeKey)...)
|
||||||
p = append(p, "\r\n"...)
|
p = append(p, "\r\n"...)
|
||||||
if c.subprotocol != "" {
|
if c.subprotocol != "" {
|
||||||
p = append(p, "Sec-Websocket-Protocol: "...)
|
p = append(p, "Sec-WebSocket-Protocol: "...)
|
||||||
p = append(p, c.subprotocol...)
|
p = append(p, c.subprotocol...)
|
||||||
p = append(p, "\r\n"...)
|
p = append(p, "\r\n"...)
|
||||||
}
|
}
|
||||||
if compress {
|
if compress {
|
||||||
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||||
}
|
}
|
||||||
for k, vs := range responseHeader {
|
for k, vs := range responseHeader {
|
||||||
if k == "Sec-Websocket-Protocol" {
|
if k == "Sec-Websocket-Protocol" {
|
||||||
@@ -230,13 +264,14 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
|
|
||||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
//
|
//
|
||||||
// This function is deprecated, use websocket.Upgrader instead.
|
// Deprecated: Use websocket.Upgrader instead.
|
||||||
//
|
//
|
||||||
// The application is responsible for checking the request origin before
|
// Upgrade does not perform origin checking. The application is responsible for
|
||||||
// calling Upgrade. An example implementation of the same origin policy is:
|
// checking the Origin header before calling Upgrade. An example implementation
|
||||||
|
// of the same origin policy check is:
|
||||||
//
|
//
|
||||||
// if req.Header.Get("Origin") != "http://"+req.Host {
|
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||||
// http.Error(w, "Origin not allowed", 403)
|
// http.Error(w, "Origin not allowed", http.StatusForbidden)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -289,3 +324,40 @@ func IsWebSocketUpgrade(r *http.Request) bool {
|
|||||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||||
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bufioReaderSize size returns the size of a bufio.Reader.
|
||||||
|
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
|
||||||
|
// This code assumes that peek on a reset reader returns
|
||||||
|
// bufio.Reader.buf[:0].
|
||||||
|
// TODO: Use bufio.Reader.Size() after Go 1.10
|
||||||
|
br.Reset(originalReader)
|
||||||
|
if p, err := br.Peek(0); err == nil {
|
||||||
|
return cap(p)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHook is an io.Writer that records the last slice passed to it vio
|
||||||
|
// io.Writer.Write.
|
||||||
|
type writeHook struct {
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||||
|
wh.p = p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
|
||||||
|
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
|
||||||
|
// This code assumes that bufio.Writer.buf[:1] is passed to the
|
||||||
|
// bufio.Writer's underlying writer.
|
||||||
|
var wh writeHook
|
||||||
|
bw.Reset(&wh)
|
||||||
|
bw.WriteByte(0)
|
||||||
|
bw.Flush()
|
||||||
|
|
||||||
|
bw.Reset(originalWriter)
|
||||||
|
|
||||||
|
return wh.p[:cap(wh.p)]
|
||||||
|
}
|
||||||
|
|||||||
19
vendor/github.com/gorilla/websocket/trace.go
generated
vendored
Normal file
19
vendor/github.com/gorilla/websocket/trace.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http/httptrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if trace.TLSHandshakeStart != nil {
|
||||||
|
trace.TLSHandshakeStart()
|
||||||
|
}
|
||||||
|
err := doHandshake(tlsConn, cfg)
|
||||||
|
if trace.TLSHandshakeDone != nil {
|
||||||
|
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
12
vendor/github.com/gorilla/websocket/trace_17.go
generated
vendored
Normal file
12
vendor/github.com/gorilla/websocket/trace_17.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http/httptrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
return doHandshake(tlsConn, cfg)
|
||||||
|
}
|
||||||
35
vendor/github.com/gorilla/websocket/util.go
generated
vendored
35
vendor/github.com/gorilla/websocket/util.go
generated
vendored
@@ -11,6 +11,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||||
@@ -111,14 +112,14 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
|||||||
case escape:
|
case escape:
|
||||||
escape = false
|
escape = false
|
||||||
p[j] = b
|
p[j] = b
|
||||||
j += 1
|
j++
|
||||||
case b == '\\':
|
case b == '\\':
|
||||||
escape = true
|
escape = true
|
||||||
case b == '"':
|
case b == '"':
|
||||||
return string(p[:j]), s[i+1:]
|
return string(p[:j]), s[i+1:]
|
||||||
default:
|
default:
|
||||||
p[j] = b
|
p[j] = b
|
||||||
j += 1
|
j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", ""
|
return "", ""
|
||||||
@@ -127,8 +128,31 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
||||||
|
func equalASCIIFold(s, t string) bool {
|
||||||
|
for s != "" && t != "" {
|
||||||
|
sr, size := utf8.DecodeRuneInString(s)
|
||||||
|
s = s[size:]
|
||||||
|
tr, size := utf8.DecodeRuneInString(t)
|
||||||
|
t = t[size:]
|
||||||
|
if sr == tr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= sr && sr <= 'Z' {
|
||||||
|
sr = sr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if 'A' <= tr && tr <= 'Z' {
|
||||||
|
tr = tr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if sr != tr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s == t
|
||||||
|
}
|
||||||
|
|
||||||
// tokenListContainsValue returns true if the 1#token header with the given
|
// tokenListContainsValue returns true if the 1#token header with the given
|
||||||
// name contains token.
|
// name contains a token equal to value with ASCII case folding.
|
||||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||||
headers:
|
headers:
|
||||||
for _, s := range header[name] {
|
for _, s := range header[name] {
|
||||||
@@ -142,7 +166,7 @@ headers:
|
|||||||
if s != "" && s[0] != ',' {
|
if s != "" && s[0] != ',' {
|
||||||
continue headers
|
continue headers
|
||||||
}
|
}
|
||||||
if strings.EqualFold(t, value) {
|
if equalASCIIFold(t, value) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if s == "" {
|
if s == "" {
|
||||||
@@ -154,9 +178,8 @@ headers:
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseExtensiosn parses WebSocket extensions from a header.
|
// parseExtensions parses WebSocket extensions from a header.
|
||||||
func parseExtensions(header http.Header) []map[string]string {
|
func parseExtensions(header http.Header) []map[string]string {
|
||||||
|
|
||||||
// From RFC 6455:
|
// From RFC 6455:
|
||||||
//
|
//
|
||||||
// Sec-WebSocket-Extensions = extension-list
|
// Sec-WebSocket-Extensions = extension-list
|
||||||
|
|||||||
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
||||||
|
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
|
||||||
|
|
||||||
|
// Package proxy provides support for a variety of protocols to proxy network
|
||||||
|
// data.
|
||||||
|
//
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxy_direct struct{}
|
||||||
|
|
||||||
|
// Direct is a direct proxy: one that makes network connections directly.
|
||||||
|
var proxy_Direct = proxy_direct{}
|
||||||
|
|
||||||
|
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
|
// requested matches one of a number of exceptions.
|
||||||
|
type proxy_PerHost struct {
|
||||||
|
def, bypass proxy_Dialer
|
||||||
|
|
||||||
|
bypassNetworks []*net.IPNet
|
||||||
|
bypassIPs []net.IP
|
||||||
|
bypassZones []string
|
||||||
|
bypassHosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||||
|
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||||
|
// the configured rules.
|
||||||
|
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
|
||||||
|
return &proxy_PerHost{
|
||||||
|
def: defaultDialer,
|
||||||
|
bypass: bypass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.dialerForRequest(host).Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
for _, net := range p.bypassNetworks {
|
||||||
|
if net.Contains(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassIP := range p.bypassIPs {
|
||||||
|
if bypassIP.Equal(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range p.bypassZones {
|
||||||
|
if strings.HasSuffix(host, zone) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
if host == zone[1:] {
|
||||||
|
// For a zone ".example.com", we match "example.com"
|
||||||
|
// too.
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassHost := range p.bypassHosts {
|
||||||
|
if bypassHost == host {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromString parses a string that contains comma-separated values
|
||||||
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
|
// ignored.
|
||||||
|
func (p *proxy_PerHost) AddFromString(s string) {
|
||||||
|
hosts := strings.Split(s, ",")
|
||||||
|
for _, host := range hosts {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "/") {
|
||||||
|
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||||
|
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||||
|
p.AddNetwork(net)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
p.AddIP(ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
p.AddZone(host[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.AddHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match an IP.
|
||||||
|
func (p *proxy_PerHost) AddIP(ip net.IP) {
|
||||||
|
p.bypassIPs = append(p.bypassIPs, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match.
|
||||||
|
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
|
||||||
|
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||||
|
// "example.com" matches "example.com" and all of its subdomains.
|
||||||
|
func (p *proxy_PerHost) AddZone(zone string) {
|
||||||
|
if strings.HasSuffix(zone, ".") {
|
||||||
|
zone = zone[:len(zone)-1]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(zone, ".") {
|
||||||
|
zone = "." + zone
|
||||||
|
}
|
||||||
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
|
func (p *proxy_PerHost) AddHost(host string) {
|
||||||
|
if strings.HasSuffix(host, ".") {
|
||||||
|
host = host[:len(host)-1]
|
||||||
|
}
|
||||||
|
p.bypassHosts = append(p.bypassHosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer is a means to establish a connection.
|
||||||
|
type proxy_Dialer interface {
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth contains authentication parameters that specific Dialers may require.
|
||||||
|
type proxy_Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||||
|
// the environment.
|
||||||
|
func proxy_FromEnvironment() proxy_Dialer {
|
||||||
|
allProxy := proxy_allProxyEnv.Get()
|
||||||
|
if len(allProxy) == 0 {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(allProxy)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxy := proxy_noProxyEnv.Get()
|
||||||
|
if len(noProxy) == 0 {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
perHost := proxy_NewPerHost(proxy, proxy_Direct)
|
||||||
|
perHost.AddFromString(noProxy)
|
||||||
|
return perHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||||
|
// from a URL with such a scheme.
|
||||||
|
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
|
||||||
|
|
||||||
|
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||||
|
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||||
|
// by FromURL.
|
||||||
|
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
|
||||||
|
if proxy_proxySchemes == nil {
|
||||||
|
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
|
||||||
|
}
|
||||||
|
proxy_proxySchemes[scheme] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromURL returns a Dialer given a URL specification and an underlying
|
||||||
|
// Dialer for it to make network requests.
|
||||||
|
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
var auth *proxy_Auth
|
||||||
|
if u.User != nil {
|
||||||
|
auth = new(proxy_Auth)
|
||||||
|
auth.User = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
auth.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "socks5":
|
||||||
|
return proxy_SOCKS5("tcp", u.Host, auth, forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||||
|
// was registered by another package.
|
||||||
|
if proxy_proxySchemes != nil {
|
||||||
|
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
|
||||||
|
return f(u, forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
proxy_allProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
proxy_noProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type proxy_envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
|
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
s := &proxy_socks5{
|
||||||
|
network: network,
|
||||||
|
addr: addr,
|
||||||
|
forward: forward,
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
s.user = auth.User
|
||||||
|
s.password = auth.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy_socks5 struct {
|
||||||
|
user, password string
|
||||||
|
network, addr string
|
||||||
|
forward proxy_Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy_socks5Version = 5
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5AuthNone = 0
|
||||||
|
proxy_socks5AuthPassword = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const proxy_socks5Connect = 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5IP4 = 1
|
||||||
|
proxy_socks5Domain = 3
|
||||||
|
proxy_socks5IP6 = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxy_socks5Errors = []string{
|
||||||
|
"",
|
||||||
|
"general failure",
|
||||||
|
"connection forbidden",
|
||||||
|
"network unreachable",
|
||||||
|
"host unreachable",
|
||||||
|
"connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"command not supported",
|
||||||
|
"address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
|
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.forward.Dial(s.network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.connect(conn, addr); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
|
||||||
|
host, portStr, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, proxy_socks5Version)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
|
if buf[1] == proxy_socks5AuthPassword {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, proxy_socks5IP4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, proxy_socks5IP6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, proxy_socks5Domain)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(proxy_socks5Errors) {
|
||||||
|
failure = proxy_socks5Errors[buf[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case proxy_socks5IP4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case proxy_socks5IP6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case proxy_socks5Domain:
|
||||||
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also need to discard the port number
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
476
vendor/github.com/klauspost/cpuid/private-gen.go
generated
vendored
476
vendor/github.com/klauspost/cpuid/private-gen.go
generated
vendored
@@ -1,476 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"go/printer"
|
|
||||||
"go/token"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var inFiles = []string{"cpuid.go", "cpuid_test.go"}
|
|
||||||
var copyFiles = []string{"cpuid_amd64.s", "cpuid_386.s", "detect_ref.go", "detect_intel.go"}
|
|
||||||
var fileSet = token.NewFileSet()
|
|
||||||
var reWrites = []rewrite{
|
|
||||||
initRewrite("CPUInfo -> cpuInfo"),
|
|
||||||
initRewrite("Vendor -> vendor"),
|
|
||||||
initRewrite("Flags -> flags"),
|
|
||||||
initRewrite("Detect -> detect"),
|
|
||||||
initRewrite("CPU -> cpu"),
|
|
||||||
}
|
|
||||||
var excludeNames = map[string]bool{"string": true, "join": true, "trim": true,
|
|
||||||
// cpuid_test.go
|
|
||||||
"t": true, "println": true, "logf": true, "log": true, "fatalf": true, "fatal": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var excludePrefixes = []string{"test", "benchmark"}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
Package := "private"
|
|
||||||
parserMode := parser.ParseComments
|
|
||||||
exported := make(map[string]rewrite)
|
|
||||||
for _, file := range inFiles {
|
|
||||||
in, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("opening input", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
src, err := ioutil.ReadAll(in)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("reading input", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
astfile, err := parser.ParseFile(fileSet, file, src, parserMode)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("parsing input", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rw := range reWrites {
|
|
||||||
astfile = rw(astfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect the AST and print all identifiers and literals.
|
|
||||||
var startDecl token.Pos
|
|
||||||
var endDecl token.Pos
|
|
||||||
ast.Inspect(astfile, func(n ast.Node) bool {
|
|
||||||
var s string
|
|
||||||
switch x := n.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
if x.IsExported() {
|
|
||||||
t := strings.ToLower(x.Name)
|
|
||||||
for _, pre := range excludePrefixes {
|
|
||||||
if strings.HasPrefix(t, pre) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if excludeNames[t] != true {
|
|
||||||
//if x.Pos() > startDecl && x.Pos() < endDecl {
|
|
||||||
exported[x.Name] = initRewrite(x.Name + " -> " + t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.GenDecl:
|
|
||||||
if x.Tok == token.CONST && x.Lparen > 0 {
|
|
||||||
startDecl = x.Lparen
|
|
||||||
endDecl = x.Rparen
|
|
||||||
// fmt.Printf("Decl:%s -> %s\n", fileSet.Position(startDecl), fileSet.Position(endDecl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s != "" {
|
|
||||||
fmt.Printf("%s:\t%s\n", fileSet.Position(n.Pos()), s)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, rw := range exported {
|
|
||||||
astfile = rw(astfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
printer.Fprint(&buf, fileSet, astfile)
|
|
||||||
|
|
||||||
// Remove package documentation and insert information
|
|
||||||
s := buf.String()
|
|
||||||
ind := strings.Index(buf.String(), "\npackage cpuid")
|
|
||||||
s = s[ind:]
|
|
||||||
s = "// Generated, DO NOT EDIT,\n" +
|
|
||||||
"// but copy it to your own project and rename the package.\n" +
|
|
||||||
"// See more at http://github.com/klauspost/cpuid\n" +
|
|
||||||
s
|
|
||||||
|
|
||||||
outputName := Package + string(os.PathSeparator) + file
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(outputName, []byte(s), 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("writing output: %s", err)
|
|
||||||
}
|
|
||||||
log.Println("Generated", outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range copyFiles {
|
|
||||||
dst := ""
|
|
||||||
if strings.HasPrefix(file, "cpuid") {
|
|
||||||
dst = Package + string(os.PathSeparator) + file
|
|
||||||
} else {
|
|
||||||
dst = Package + string(os.PathSeparator) + "cpuid_" + file
|
|
||||||
}
|
|
||||||
err := copyFile(file, dst)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("copying file: %s", err)
|
|
||||||
}
|
|
||||||
log.Println("Copied", dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile copies a file from src to dst. If src and dst files exist, and are
|
|
||||||
// the same, then return success. Copy the file contents from src to dst.
|
|
||||||
func copyFile(src, dst string) (err error) {
|
|
||||||
sfi, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !sfi.Mode().IsRegular() {
|
|
||||||
// cannot copy non-regular files (e.g., directories,
|
|
||||||
// symlinks, devices, etc.)
|
|
||||||
return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
|
|
||||||
}
|
|
||||||
dfi, err := os.Stat(dst)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !(dfi.Mode().IsRegular()) {
|
|
||||||
return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
|
|
||||||
}
|
|
||||||
if os.SameFile(sfi, dfi) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = copyFileContents(src, dst)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyFileContents copies the contents of the file named src to the file named
|
|
||||||
// by dst. The file will be created if it does not already exist. If the
|
|
||||||
// destination file exists, all it's contents will be replaced by the contents
|
|
||||||
// of the source file.
|
|
||||||
func copyFileContents(src, dst string) (err error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
cerr := out.Close()
|
|
||||||
if err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if _, err = io.Copy(out, in); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = out.Sync()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type rewrite func(*ast.File) *ast.File
|
|
||||||
|
|
||||||
// Mostly copied from gofmt
|
|
||||||
func initRewrite(rewriteRule string) rewrite {
|
|
||||||
f := strings.Split(rewriteRule, "->")
|
|
||||||
if len(f) != 2 {
|
|
||||||
fmt.Fprintf(os.Stderr, "rewrite rule must be of the form 'pattern -> replacement'\n")
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
pattern := parseExpr(f[0], "pattern")
|
|
||||||
replace := parseExpr(f[1], "replacement")
|
|
||||||
return func(p *ast.File) *ast.File { return rewriteFile(pattern, replace, p) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseExpr parses s as an expression.
|
|
||||||
// It might make sense to expand this to allow statement patterns,
|
|
||||||
// but there are problems with preserving formatting and also
|
|
||||||
// with what a wildcard for a statement looks like.
|
|
||||||
func parseExpr(s, what string) ast.Expr {
|
|
||||||
x, err := parser.ParseExpr(s)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "parsing %s %s at %s\n", what, s, err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep this function for debugging.
|
|
||||||
/*
|
|
||||||
func dump(msg string, val reflect.Value) {
|
|
||||||
fmt.Printf("%s:\n", msg)
|
|
||||||
ast.Print(fileSet, val.Interface())
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rewriteFile applies the rewrite rule 'pattern -> replace' to an entire file.
|
|
||||||
func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File {
|
|
||||||
cmap := ast.NewCommentMap(fileSet, p, p.Comments)
|
|
||||||
m := make(map[string]reflect.Value)
|
|
||||||
pat := reflect.ValueOf(pattern)
|
|
||||||
repl := reflect.ValueOf(replace)
|
|
||||||
|
|
||||||
var rewriteVal func(val reflect.Value) reflect.Value
|
|
||||||
rewriteVal = func(val reflect.Value) reflect.Value {
|
|
||||||
// don't bother if val is invalid to start with
|
|
||||||
if !val.IsValid() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
for k := range m {
|
|
||||||
delete(m, k)
|
|
||||||
}
|
|
||||||
val = apply(rewriteVal, val)
|
|
||||||
if match(m, pat, val) {
|
|
||||||
val = subst(m, repl, reflect.ValueOf(val.Interface().(ast.Node).Pos()))
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
r := apply(rewriteVal, reflect.ValueOf(p)).Interface().(*ast.File)
|
|
||||||
r.Comments = cmap.Filter(r).Comments() // recreate comments list
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// set is a wrapper for x.Set(y); it protects the caller from panics if x cannot be changed to y.
|
|
||||||
func set(x, y reflect.Value) {
|
|
||||||
// don't bother if x cannot be set or y is invalid
|
|
||||||
if !x.CanSet() || !y.IsValid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if x := recover(); x != nil {
|
|
||||||
if s, ok := x.(string); ok &&
|
|
||||||
(strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) {
|
|
||||||
// x cannot be set to y - ignore this rewrite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(x)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
x.Set(y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values/types for special cases.
|
|
||||||
var (
|
|
||||||
objectPtrNil = reflect.ValueOf((*ast.Object)(nil))
|
|
||||||
scopePtrNil = reflect.ValueOf((*ast.Scope)(nil))
|
|
||||||
|
|
||||||
identType = reflect.TypeOf((*ast.Ident)(nil))
|
|
||||||
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
|
|
||||||
positionType = reflect.TypeOf(token.NoPos)
|
|
||||||
callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
|
|
||||||
scopePtrType = reflect.TypeOf((*ast.Scope)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
// apply replaces each AST field x in val with f(x), returning val.
|
|
||||||
// To avoid extra conversions, f operates on the reflect.Value form.
|
|
||||||
func apply(f func(reflect.Value) reflect.Value, val reflect.Value) reflect.Value {
|
|
||||||
if !val.IsValid() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// *ast.Objects introduce cycles and are likely incorrect after
|
|
||||||
// rewrite; don't follow them but replace with nil instead
|
|
||||||
if val.Type() == objectPtrType {
|
|
||||||
return objectPtrNil
|
|
||||||
}
|
|
||||||
|
|
||||||
// similarly for scopes: they are likely incorrect after a rewrite;
|
|
||||||
// replace them with nil
|
|
||||||
if val.Type() == scopePtrType {
|
|
||||||
return scopePtrNil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := reflect.Indirect(val); v.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
e := v.Index(i)
|
|
||||||
set(e, f(e))
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
e := v.Field(i)
|
|
||||||
set(e, f(e))
|
|
||||||
}
|
|
||||||
case reflect.Interface:
|
|
||||||
e := v.Elem()
|
|
||||||
set(v, f(e))
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func isWildcard(s string) bool {
|
|
||||||
rune, size := utf8.DecodeRuneInString(s)
|
|
||||||
return size == len(s) && unicode.IsLower(rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
// match returns true if pattern matches val,
|
|
||||||
// recording wildcard submatches in m.
|
|
||||||
// If m == nil, match checks whether pattern == val.
|
|
||||||
func match(m map[string]reflect.Value, pattern, val reflect.Value) bool {
|
|
||||||
// Wildcard matches any expression. If it appears multiple
|
|
||||||
// times in the pattern, it must match the same expression
|
|
||||||
// each time.
|
|
||||||
if m != nil && pattern.IsValid() && pattern.Type() == identType {
|
|
||||||
name := pattern.Interface().(*ast.Ident).Name
|
|
||||||
if isWildcard(name) && val.IsValid() {
|
|
||||||
// wildcards only match valid (non-nil) expressions.
|
|
||||||
if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {
|
|
||||||
if old, ok := m[name]; ok {
|
|
||||||
return match(nil, old, val)
|
|
||||||
}
|
|
||||||
m[name] = val
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, pattern and val must match recursively.
|
|
||||||
if !pattern.IsValid() || !val.IsValid() {
|
|
||||||
return !pattern.IsValid() && !val.IsValid()
|
|
||||||
}
|
|
||||||
if pattern.Type() != val.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special cases.
|
|
||||||
switch pattern.Type() {
|
|
||||||
case identType:
|
|
||||||
// For identifiers, only the names need to match
|
|
||||||
// (and none of the other *ast.Object information).
|
|
||||||
// This is a common case, handle it all here instead
|
|
||||||
// of recursing down any further via reflection.
|
|
||||||
p := pattern.Interface().(*ast.Ident)
|
|
||||||
v := val.Interface().(*ast.Ident)
|
|
||||||
return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
|
|
||||||
case objectPtrType, positionType:
|
|
||||||
// object pointers and token positions always match
|
|
||||||
return true
|
|
||||||
case callExprType:
|
|
||||||
// For calls, the Ellipsis fields (token.Position) must
|
|
||||||
// match since that is how f(x) and f(x...) are different.
|
|
||||||
// Check them here but fall through for the remaining fields.
|
|
||||||
p := pattern.Interface().(*ast.CallExpr)
|
|
||||||
v := val.Interface().(*ast.CallExpr)
|
|
||||||
if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := reflect.Indirect(pattern)
|
|
||||||
v := reflect.Indirect(val)
|
|
||||||
if !p.IsValid() || !v.IsValid() {
|
|
||||||
return !p.IsValid() && !v.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
if p.Len() != v.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := 0; i < p.Len(); i++ {
|
|
||||||
if !match(m, p.Index(i), v.Index(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < p.NumField(); i++ {
|
|
||||||
if !match(m, p.Field(i), v.Field(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
return match(m, p.Elem(), v.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle token integers, etc.
|
|
||||||
return p.Interface() == v.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// subst returns a copy of pattern with values from m substituted in place
|
|
||||||
// of wildcards and pos used as the position of tokens from the pattern.
|
|
||||||
// if m == nil, subst returns a copy of pattern and doesn't change the line
|
|
||||||
// number information.
|
|
||||||
func subst(m map[string]reflect.Value, pattern reflect.Value, pos reflect.Value) reflect.Value {
|
|
||||||
if !pattern.IsValid() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wildcard gets replaced with map value.
|
|
||||||
if m != nil && pattern.Type() == identType {
|
|
||||||
name := pattern.Interface().(*ast.Ident).Name
|
|
||||||
if isWildcard(name) {
|
|
||||||
if old, ok := m[name]; ok {
|
|
||||||
return subst(nil, old, reflect.Value{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pos.IsValid() && pattern.Type() == positionType {
|
|
||||||
// use new position only if old position was valid in the first place
|
|
||||||
if old := pattern.Interface().(token.Pos); !old.IsValid() {
|
|
||||||
return pattern
|
|
||||||
}
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise copy.
|
|
||||||
switch p := pattern; p.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
v := reflect.MakeSlice(p.Type(), p.Len(), p.Len())
|
|
||||||
for i := 0; i < p.Len(); i++ {
|
|
||||||
v.Index(i).Set(subst(m, p.Index(i), pos))
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
v := reflect.New(p.Type()).Elem()
|
|
||||||
for i := 0; i < p.NumField(); i++ {
|
|
||||||
v.Field(i).Set(subst(m, p.Field(i), pos))
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
v := reflect.New(p.Type()).Elem()
|
|
||||||
if elem := p.Elem(); elem.IsValid() {
|
|
||||||
v.Set(subst(m, elem, pos).Addr())
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
v := reflect.New(p.Type()).Elem()
|
|
||||||
if elem := p.Elem(); elem.IsValid() {
|
|
||||||
v.Set(subst(m, elem, pos))
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
return pattern
|
|
||||||
}
|
|
||||||
132
vendor/github.com/klauspost/reedsolomon/gentables.go
generated
vendored
132
vendor/github.com/klauspost/reedsolomon/gentables.go
generated
vendored
@@ -1,132 +0,0 @@
|
|||||||
//+build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logTable = [fieldSize]int16{
|
|
||||||
-1, 0, 1, 25, 2, 50, 26, 198,
|
|
||||||
3, 223, 51, 238, 27, 104, 199, 75,
|
|
||||||
4, 100, 224, 14, 52, 141, 239, 129,
|
|
||||||
28, 193, 105, 248, 200, 8, 76, 113,
|
|
||||||
5, 138, 101, 47, 225, 36, 15, 33,
|
|
||||||
53, 147, 142, 218, 240, 18, 130, 69,
|
|
||||||
29, 181, 194, 125, 106, 39, 249, 185,
|
|
||||||
201, 154, 9, 120, 77, 228, 114, 166,
|
|
||||||
6, 191, 139, 98, 102, 221, 48, 253,
|
|
||||||
226, 152, 37, 179, 16, 145, 34, 136,
|
|
||||||
54, 208, 148, 206, 143, 150, 219, 189,
|
|
||||||
241, 210, 19, 92, 131, 56, 70, 64,
|
|
||||||
30, 66, 182, 163, 195, 72, 126, 110,
|
|
||||||
107, 58, 40, 84, 250, 133, 186, 61,
|
|
||||||
202, 94, 155, 159, 10, 21, 121, 43,
|
|
||||||
78, 212, 229, 172, 115, 243, 167, 87,
|
|
||||||
7, 112, 192, 247, 140, 128, 99, 13,
|
|
||||||
103, 74, 222, 237, 49, 197, 254, 24,
|
|
||||||
227, 165, 153, 119, 38, 184, 180, 124,
|
|
||||||
17, 68, 146, 217, 35, 32, 137, 46,
|
|
||||||
55, 63, 209, 91, 149, 188, 207, 205,
|
|
||||||
144, 135, 151, 178, 220, 252, 190, 97,
|
|
||||||
242, 86, 211, 171, 20, 42, 93, 158,
|
|
||||||
132, 60, 57, 83, 71, 109, 65, 162,
|
|
||||||
31, 45, 67, 216, 183, 123, 164, 118,
|
|
||||||
196, 23, 73, 236, 127, 12, 111, 246,
|
|
||||||
108, 161, 59, 82, 41, 157, 85, 170,
|
|
||||||
251, 96, 134, 177, 187, 204, 62, 90,
|
|
||||||
203, 89, 95, 176, 156, 169, 160, 81,
|
|
||||||
11, 245, 22, 235, 122, 117, 44, 215,
|
|
||||||
79, 174, 213, 233, 230, 231, 173, 232,
|
|
||||||
116, 214, 244, 234, 168, 80, 88, 175,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// The number of elements in the field.
|
|
||||||
fieldSize = 256
|
|
||||||
|
|
||||||
// The polynomial used to generate the logarithm table.
|
|
||||||
//
|
|
||||||
// There are a number of polynomials that work to generate
|
|
||||||
// a Galois field of 256 elements. The choice is arbitrary,
|
|
||||||
// and we just use the first one.
|
|
||||||
//
|
|
||||||
// The possibilities are: 29, 43, 45, 77, 95, 99, 101, 105,
|
|
||||||
//* 113, 135, 141, 169, 195, 207, 231, and 245.
|
|
||||||
generatingPolynomial = 29
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
t := generateExpTable()
|
|
||||||
fmt.Printf("var expTable = %#v\n", t)
|
|
||||||
//t2 := generateMulTableSplit(t)
|
|
||||||
//fmt.Printf("var mulTable = %#v\n", t2)
|
|
||||||
low, high := generateMulTableHalf(t)
|
|
||||||
fmt.Printf("var mulTableLow = %#v\n", low)
|
|
||||||
fmt.Printf("var mulTableHigh = %#v\n", high)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the inverse log table.
|
|
||||||
*/
|
|
||||||
func generateExpTable() []byte {
|
|
||||||
result := make([]byte, fieldSize*2-2)
|
|
||||||
for i := 1; i < fieldSize; i++ {
|
|
||||||
log := logTable[i]
|
|
||||||
result[log] = byte(i)
|
|
||||||
result[log+fieldSize-1] = byte(i)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMulTable(expTable []byte) []byte {
|
|
||||||
result := make([]byte, 256*256)
|
|
||||||
for v := range result {
|
|
||||||
a := byte(v & 0xff)
|
|
||||||
b := byte(v >> 8)
|
|
||||||
if a == 0 || b == 0 {
|
|
||||||
result[v] = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logA := int(logTable[a])
|
|
||||||
logB := int(logTable[b])
|
|
||||||
result[v] = expTable[logA+logB]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMulTableSplit(expTable []byte) [256][256]byte {
|
|
||||||
var result [256][256]byte
|
|
||||||
for a := range result {
|
|
||||||
for b := range result[a] {
|
|
||||||
if a == 0 || b == 0 {
|
|
||||||
result[a][b] = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logA := int(logTable[a])
|
|
||||||
logB := int(logTable[b])
|
|
||||||
result[a][b] = expTable[logA+logB]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMulTableHalf(expTable []byte) (low [256][16]byte, high [256][16]byte) {
|
|
||||||
for a := range low {
|
|
||||||
for b := range low {
|
|
||||||
result := 0
|
|
||||||
if !(a == 0 || b == 0) {
|
|
||||||
logA := int(logTable[a])
|
|
||||||
logB := int(logTable[b])
|
|
||||||
result = int(expTable[logA+logB])
|
|
||||||
}
|
|
||||||
if (b & 0xf) == b {
|
|
||||||
low[a][b] = byte(result)
|
|
||||||
}
|
|
||||||
if (b & 0xf0) == b {
|
|
||||||
high[a][b>>4] = byte(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
9
vendor/github.com/pires/go-proxyproto/.gitignore
generated
vendored
Normal file
9
vendor/github.com/pires/go-proxyproto/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
.idea
|
||||||
|
bin
|
||||||
|
pkg
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user